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 { + let configuration = Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + return try await run( + configuration, + input: input, + output: output, + error: error + ) +} + +// MARK: - Custom Execution Body + +/// Run a executable with given parameters and a custom closure +/// to manage the running subprocess' lifetime and its IOs. +/// - 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: How to manage the executable standard ouput. +/// - error: How to manager executable standard error. +/// - isolation: the isolation context to run the body closure. +/// - body: The custom execution body to manually control the running process +/// - Returns a ExecutableResult type containing the return value +/// of the closure. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions(), + input: Input = .none, + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + body: ((Execution) async throws -> Result) +) async throws -> ExecutionResult where Output.OutputType == Void, Error.OutputType == Void { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(input: input, output: output, error: error, body) +} + +/// Run a executable with given parameters and a custom closure +/// to manage the running subprocess' lifetime and write to its +/// standard input via `StandardInputWriter` +/// - 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. +/// - output:How to handle executable's standard output +/// - error: How to handle executable's standard error +/// - isolation: the isolation context to run the body closure. +/// - body: The custom execution body to manually control the running process +/// - Returns a ExecutableResult type containing the return value +/// of the closure. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions(), + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + body: ((Execution, StandardInputWriter) async throws -> Result) +) async throws -> ExecutionResult where Output.OutputType == Void, Error.OutputType == Void { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(output: output, error: error, body) +} + +// MARK: - Configuration Based + +/// Run a `Configuration` asynchrously and returns +/// a `CollectedResult` containing the output of the child process. +/// - Parameters: +/// - configuration: The `Subprocess` configuration to run. +/// - 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 +>( + _ configuration: Configuration, + input: Input = .none, + output: Output = .string, + error: Error = .discarded +) async throws -> CollectedResult { + let result = try await configuration.run( + input: input, + output: output, + error: error + ) { execution in + let ( + standardOutput, + standardError + ) = try await execution.captureIOs() + return ( + processIdentifier: execution.processIdentifier, + standardOutput: standardOutput, + standardError: standardError + ) + } + return CollectedResult( + processIdentifier: result.value.processIdentifier, + terminationStatus: result.terminationStatus, + standardOutput: result.value.standardOutput, + standardError: result.value.standardError + ) +} + +/// Run a executable with given parameters specified by a `Configuration` +/// - Parameters: +/// - configuration: The `Subprocess` configuration to run. +/// - output: The method to use for redirecting the standard output. +/// - error: The method to use for redirecting the standard error. +/// - isolation: the isolation context to run the body closure. +/// - body: The custom configuration body to manually control +/// the running process and write to its standard input. +/// - Returns a ExecutableResult type containing the return value +/// of the closure. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run( + _ configuration: Configuration, + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + body: ((Execution, StandardInputWriter) async throws -> Result) +) async throws -> ExecutionResult where Output.OutputType == Void, Error.OutputType == Void { + return try await configuration.run(output: output, error: error, body) +} + +// MARK: - Detached + +/// Run a executable with given parameters and return its process +/// identifier immediately without monitoring the state of the +/// subprocess nor waiting until it exits. +/// +/// This method is useful for launching subprocesses that outlive their +/// parents (for example, daemons and trampolines). +/// +/// - Parameters: +/// - executable: The executable to run. +/// - arguments: The arguments to pass to the executable. +/// - environment: The environment to use for the process. +/// - workingDirectory: The working directory for the process. +/// - platformOptions: The platform specific options to use for the process. +/// - input: A file descriptor to bind to the subprocess' standard input. +/// - output: A file descriptor to bind to the subprocess' standard output. +/// - error: A file descriptor to bind to the subprocess' standard error. +/// - Returns: the process identifier for the subprocess. +@available(macOS 15.0, *) // FIXME: manually added availability +public func runDetached( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions(), + input: FileDescriptor? = nil, + output: FileDescriptor? = nil, + error: FileDescriptor? = nil +) throws -> ProcessIdentifier { + let config: Configuration = Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + return try runDetached(config, input: input, output: output, error: error) +} + +/// Run a executable with given configuration and return its process +/// identifier immediately without monitoring the state of the +/// subprocess nor waiting until it exits. +/// +/// This method is useful for launching subprocesses that outlive their +/// parents (for example, daemons and trampolines). +/// +/// - Parameters: +/// - configuration: The `Subprocess` configuration to run. +/// - input: A file descriptor to bind to the subprocess' standard input. +/// - output: A file descriptor to bind to the subprocess' standard output. +/// - error: A file descriptor to bind to the subprocess' standard error. +/// - Returns: the process identifier for the subprocess. +@available(macOS 15.0, *) // FIXME: manually added availability +public func runDetached( + _ configuration: Configuration, + input: FileDescriptor? = nil, + output: FileDescriptor? = nil, + error: FileDescriptor? = nil +) throws -> ProcessIdentifier { + switch (input, output, error) { + case (.none, .none, .none): + let processOutput = DiscardedOutput() + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.none, .none, .some(let errorFd)): + let processOutput = DiscardedOutput() + let processError = FileDescriptorOutput(fileDescriptor: errorFd, closeAfterSpawningProcess: false) + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.none, .some(let outputFd), .none): + let processOutput = FileDescriptorOutput(fileDescriptor: outputFd, closeAfterSpawningProcess: false) + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.none, .some(let outputFd), .some(let errorFd)): + let processOutput = FileDescriptorOutput( + fileDescriptor: outputFd, + closeAfterSpawningProcess: false + ) + let processError = FileDescriptorOutput( + fileDescriptor: errorFd, + closeAfterSpawningProcess: false + ) + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .none, .none): + let processOutput = DiscardedOutput() + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: FileDescriptorInput( + fileDescriptor: inputFd, + closeAfterSpawningProcess: false + ).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .none, .some(let errorFd)): + let processOutput = DiscardedOutput() + let processError = FileDescriptorOutput( + fileDescriptor: errorFd, + closeAfterSpawningProcess: false + ) + return try configuration.spawn( + withInput: FileDescriptorInput(fileDescriptor: inputFd, closeAfterSpawningProcess: false).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .some(let outputFd), .none): + let processOutput = FileDescriptorOutput( + fileDescriptor: outputFd, + closeAfterSpawningProcess: false + ) + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: FileDescriptorInput(fileDescriptor: inputFd, closeAfterSpawningProcess: false).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .some(let outputFd), .some(let errorFd)): + let processOutput = FileDescriptorOutput( + fileDescriptor: outputFd, + closeAfterSpawningProcess: false + ) + let processError = FileDescriptorOutput( + fileDescriptor: errorFd, + closeAfterSpawningProcess: false + ) + return try configuration.spawn( + withInput: FileDescriptorInput(fileDescriptor: inputFd, closeAfterSpawningProcess: false).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + } +} diff --git a/Sources/_Subprocess/AsyncBufferSequence.swift b/Sources/_Subprocess/AsyncBufferSequence.swift new file mode 100644 index 00000000..4316f7eb --- /dev/null +++ b/Sources/_Subprocess/AsyncBufferSequence.swift @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +internal struct AsyncBufferSequence: AsyncSequence, Sendable { + internal typealias Failure = any Swift.Error + + internal typealias Element = SequenceOutput.Buffer + + @_nonSendable + internal struct Iterator: AsyncIteratorProtocol { + internal typealias Element = SequenceOutput.Buffer + + private let fileDescriptor: TrackedFileDescriptor + private var buffer: [UInt8] + private var currentPosition: Int + private var finished: Bool + + internal init(fileDescriptor: TrackedFileDescriptor) { + self.fileDescriptor = fileDescriptor + self.buffer = [] + self.currentPosition = 0 + self.finished = false + } + + internal mutating func next() async throws -> SequenceOutput.Buffer? { + let data = try await self.fileDescriptor.wrapped.readChunk( + upToLength: readBufferSize + ) + if data == nil { + // We finished reading. Close the file descriptor now + try self.fileDescriptor.safelyClose() + return nil + } + return data + } + } + + private let fileDescriptor: TrackedFileDescriptor + + init(fileDescriptor: TrackedFileDescriptor) { + self.fileDescriptor = fileDescriptor + } + + internal func makeAsyncIterator() -> Iterator { + return Iterator(fileDescriptor: self.fileDescriptor) + } +} + +// MARK: - Page Size +import _SubprocessCShims + +#if canImport(Darwin) +import Darwin +internal import MachO.dyld + +private let _pageSize: Int = { + Int(_subprocess_vm_size()) +}() +#elseif canImport(WinSDK) +import WinSDK +private let _pageSize: Int = { + var sysInfo: SYSTEM_INFO = SYSTEM_INFO() + GetSystemInfo(&sysInfo) + return Int(sysInfo.dwPageSize) +}() +#elseif os(WASI) +// WebAssembly defines a fixed page size +private let _pageSize: Int = 65_536 +#elseif canImport(Android) +@preconcurrency import Android +private let _pageSize: Int = Int(getpagesize()) +#elseif canImport(Glibc) +@preconcurrency import Glibc +private let _pageSize: Int = Int(getpagesize()) +#elseif canImport(Musl) +@preconcurrency import Musl +private let _pageSize: Int = Int(getpagesize()) +#elseif canImport(C) +private let _pageSize: Int = Int(getpagesize()) +#endif // canImport(Darwin) + +@inline(__always) +internal var readBufferSize: Int { + return _pageSize +} diff --git a/Sources/_Subprocess/Buffer.swift b/Sources/_Subprocess/Buffer.swift new file mode 100644 index 00000000..3ce73d7e --- /dev/null +++ b/Sources/_Subprocess/Buffer.swift @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@preconcurrency internal import Dispatch + +extension SequenceOutput { + /// A immutable collection of bytes + public struct Buffer: Sendable { + #if os(Windows) + private var data: [UInt8] + + internal init(data: [UInt8]) { + self.data = data + } + #else + private var data: DispatchData + + internal init(data: DispatchData) { + self.data = data + } + #endif + } +} + +// MARK: - Properties +extension SequenceOutput.Buffer { + /// Number of bytes stored in the buffer + public var count: Int { + return self.data.count + } + + /// A Boolean value indicating whether the collection is empty. + public var isEmpty: Bool { + return self.data.isEmpty + } +} + +// MARK: - Accessors +extension SequenceOutput.Buffer { + #if !SubprocessSpan + /// Access the raw bytes stored in this buffer + /// - Parameter body: A closure with an `UnsafeRawBufferPointer` parameter that + /// points to the contiguous storage for the type. If no such storage exists, + /// the method creates it. If body has a return value, this method also returns + /// that value. The argument is valid only for the duration of the + /// closure’s SequenceOutput. + /// - Returns: The return value, if any, of the body closure parameter. + public func withUnsafeBytes( + _ body: (UnsafeRawBufferPointer) throws -> ResultType + ) rethrows -> ResultType { + return try self._withUnsafeBytes(body) + } + #endif // !SubprocessSpan + + internal func _withUnsafeBytes( + _ body: (UnsafeRawBufferPointer) throws -> ResultType + ) rethrows -> ResultType { + #if os(Windows) + return try self.data.withUnsafeBytes(body) + #else + // Although DispatchData was designed to be uncontiguous, in practice + // we found that almost all DispatchData are contiguous. Therefore + // we can access this body in O(1) most of the time. + return try self.data.withUnsafeBytes { ptr in + let bytes = UnsafeRawBufferPointer(start: ptr, count: self.data.count) + return try body(bytes) + } + #endif + } + + private enum SpanBacking { + case pointer(UnsafeBufferPointer) + case array([UInt8]) + } +} + +// MARK: - Hashable, Equatable +extension SequenceOutput.Buffer: Equatable, Hashable { + #if os(Windows) + // Compiler generated conformances + #else + public static func == (lhs: SequenceOutput.Buffer, rhs: SequenceOutput.Buffer) -> Bool { + return lhs.data.elementsEqual(rhs.data) + } + + public func hash(into hasher: inout Hasher) { + self.data.withUnsafeBytes { ptr in + let bytes = UnsafeRawBufferPointer( + start: ptr, + count: self.data.count + ) + hasher.combine(bytes: bytes) + } + } + #endif +} diff --git a/Sources/_Subprocess/Configuration.swift b/Sources/_Subprocess/Configuration.swift new file mode 100644 index 00000000..4dad1a47 --- /dev/null +++ b/Sources/_Subprocess/Configuration.swift @@ -0,0 +1,851 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +internal import Dispatch + +/// A collection of configurations parameters to use when +/// spawning a subprocess. +public struct Configuration: Sendable { + /// The executable to run. + public var executable: Executable + /// The arguments to pass to the executable. + public var arguments: Arguments + /// The environment to use when running the executable. + public var environment: Environment + /// The working directory to use when running the executable. + public var workingDirectory: FilePath + /// The platform specifc options to use when + /// running the subprocess. + public var platformOptions: PlatformOptions + + public init( + executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions() + ) { + self.executable = executable + self.arguments = arguments + self.environment = environment + self.workingDirectory = workingDirectory ?? .currentWorkingDirectory + self.platformOptions = platformOptions + } + + @available(macOS 15.0, *) // FIXME: manually added availability + internal func run< + Result, + Output: OutputProtocol, + Error: OutputProtocol + >( + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + _ body: ( + Execution, + StandardInputWriter + ) async throws -> Result + ) async throws -> ExecutionResult { + let input = CustomWriteInput() + + let inputPipe = try input.createPipe() + let outputPipe = try output.createPipe() + let errorPipe = try error.createPipe() + + let execution = try self.spawn( + withInput: inputPipe, + output: output, + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe + ) + // After spawn, cleanup child side fds + try await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: true, + parentSide: false, + attemptToTerminateSubProcess: false + ) + return try await withAsyncTaskCleanupHandler { + async let waitingStatus = try await monitorProcessTermination( + forProcessWithIdentifier: execution.processIdentifier + ) + // Body runs in the same isolation + let result = try await body( + execution, + .init(fileDescriptor: inputPipe.writeFileDescriptor!) + ) + return ExecutionResult( + terminationStatus: try await waitingStatus, + value: result + ) + } onCleanup: { + // Attempt to terminate the child process + // Since the task has already been cancelled, + // this is the best we can do + try? await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: false, + parentSide: true, + attemptToTerminateSubProcess: true + ) + } + } + + @available(macOS 15.0, *) // FIXME: manually added availability + internal func run< + Result, + Input: InputProtocol, + Output: OutputProtocol, + Error: OutputProtocol + >( + input: Input, + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + _ body: ((Execution) async throws -> Result) + ) async throws -> ExecutionResult { + + let inputPipe = try input.createPipe() + let outputPipe = try output.createPipe() + let errorPipe = try error.createPipe() + + let execution = try self.spawn( + withInput: inputPipe, + output: output, + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe + ) + // After spawn, clean up child side + try await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: true, + parentSide: false, + attemptToTerminateSubProcess: false + ) + + return try await withAsyncTaskCleanupHandler { + return try await withThrowingTaskGroup( + of: TerminationStatus?.self, + returning: ExecutionResult.self + ) { group in + group.addTask { + if let writeFd = inputPipe.writeFileDescriptor { + let writer = StandardInputWriter(fileDescriptor: writeFd) + try await input.write(with: writer) + try await writer.finish() + } + return nil + } + group.addTask { + return try await monitorProcessTermination( + forProcessWithIdentifier: execution.processIdentifier + ) + } + + // Body runs in the same isolation + let result = try await body(execution) + var status: TerminationStatus? = nil + while let monitorResult = try await group.next() { + if let monitorResult = monitorResult { + status = monitorResult + } + } + return ExecutionResult(terminationStatus: status!, value: result) + } + } onCleanup: { + // Attempt to terminate the child process + // Since the task has already been cancelled, + // this is the best we can do + try? await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: false, + parentSide: true, + attemptToTerminateSubProcess: true + ) + } + } +} + +extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + return """ + Configuration( + executable: \(self.executable.description), + arguments: \(self.arguments.description), + environment: \(self.environment.description), + workingDirectory: \(self.workingDirectory), + platformOptions: \(self.platformOptions.description(withIndent: 1)) + ) + """ + } + + public var debugDescription: String { + return """ + Configuration( + executable: \(self.executable.debugDescription), + arguments: \(self.arguments.debugDescription), + environment: \(self.environment.debugDescription), + workingDirectory: \(self.workingDirectory), + platformOptions: \(self.platformOptions.description(withIndent: 1)) + ) + """ + } +} + +// MARK: - Cleanup +extension Configuration { + /// Close each input individually, and throw the first error if there's multiple errors thrown + @Sendable + @available(macOS 15.0, *) // FIXME: manually added availability + private func cleanup< + Output: OutputProtocol, + Error: OutputProtocol + >( + execution: Execution, + inputPipe: CreatedPipe, + outputPipe: CreatedPipe, + errorPipe: CreatedPipe, + childSide: Bool, + parentSide: Bool, + attemptToTerminateSubProcess: Bool + ) async throws { + func captureError(_ work: () throws -> Void) -> Swift.Error? { + do { + try work() + return nil + } catch { + // Ignore badFileDescriptor for double close + return error + } + } + + guard childSide || parentSide || attemptToTerminateSubProcess else { + return + } + + // Attempt to teardown the subprocess + if attemptToTerminateSubProcess { + await execution.teardown( + using: self.platformOptions.teardownSequence + ) + } + + var inputError: Swift.Error? + var outputError: Swift.Error? + var errorError: Swift.Error? // lol + + if childSide { + inputError = captureError { + try inputPipe.readFileDescriptor?.safelyClose() + } + outputError = captureError { + try outputPipe.writeFileDescriptor?.safelyClose() + } + errorError = captureError { + try errorPipe.writeFileDescriptor?.safelyClose() + } + } + + if parentSide { + inputError = captureError { + try inputPipe.writeFileDescriptor?.safelyClose() + } + outputError = captureError { + try outputPipe.readFileDescriptor?.safelyClose() + } + errorError = captureError { + try errorPipe.readFileDescriptor?.safelyClose() + } + } + + if let inputError = inputError { + throw inputError + } + + if let outputError = outputError { + throw outputError + } + + if let errorError = errorError { + throw errorError + } + } + + /// Close each input individually, and throw the first error if there's multiple errors thrown + @Sendable + internal func cleanupPreSpawn( + input: CreatedPipe, + output: CreatedPipe, + error: CreatedPipe + ) throws { + var inputError: Swift.Error? + var outputError: Swift.Error? + var errorError: Swift.Error? + + do { + try input.readFileDescriptor?.safelyClose() + try input.writeFileDescriptor?.safelyClose() + } catch { + inputError = error + } + + do { + try output.readFileDescriptor?.safelyClose() + try output.writeFileDescriptor?.safelyClose() + } catch { + outputError = error + } + + do { + try error.readFileDescriptor?.safelyClose() + try error.writeFileDescriptor?.safelyClose() + } catch { + errorError = error + } + + if let inputError = inputError { + throw inputError + } + if let outputError = outputError { + throw outputError + } + if let errorError = errorError { + throw errorError + } + } +} + +// MARK: - Executable + +/// `Executable` defines how the executable should +/// be looked up for execution. +public struct Executable: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case executable(String) + case path(FilePath) + } + + internal let storage: Storage + + private init(_config: Storage) { + self.storage = _config + } + + /// Locate the executable by its name. + /// `Subprocess` will use `PATH` value to + /// determine the full path to the executable. + public static func name(_ executableName: String) -> Self { + return .init(_config: .executable(executableName)) + } + /// Locate the executable by its full path. + /// `Subprocess` will use this path directly. + public static func path(_ filePath: FilePath) -> Self { + return .init(_config: .path(filePath)) + } + /// Returns the full executable path given the environment value. + public func resolveExecutablePath(in environment: Environment) throws -> FilePath { + let path = try self.resolveExecutablePath(withPathValue: environment.pathValue()) + return FilePath(path) + } +} + +extension Executable: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + switch storage { + case .executable(let executableName): + return executableName + case .path(let filePath): + return filePath.string + } + } + + public var debugDescription: String { + switch storage { + case .executable(let string): + return "executable(\(string))" + case .path(let filePath): + return "path(\(filePath.string))" + } + } +} + +extension Executable { + internal func possibleExecutablePaths( + withPathValue pathValue: String? + ) -> Set { + switch self.storage { + case .executable(let executableName): + #if os(Windows) + // Windows CreateProcessW accepts executable name directly + return Set([executableName]) + #else + var results: Set = [] + // executableName could be a full path + results.insert(executableName) + // Get $PATH from environment + let searchPaths: Set + if let pathValue = pathValue { + let localSearchPaths = pathValue.split(separator: ":").map { String($0) } + searchPaths = Set(localSearchPaths).union(Self.defaultSearchPaths) + } else { + searchPaths = Self.defaultSearchPaths + } + for path in searchPaths { + results.insert( + FilePath(path).appending(executableName).string + ) + } + return results + #endif + case .path(let executablePath): + return Set([executablePath.string]) + } + } +} + +// MARK: - Arguments + +/// A collection of arguments to pass to the subprocess. +public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { + public typealias ArrayLiteralElement = String + + internal let storage: [StringOrRawBytes] + internal let executablePathOverride: StringOrRawBytes? + + /// Create an Arguments object using the given literal values + public init(arrayLiteral elements: String...) { + self.storage = elements.map { .string($0) } + self.executablePathOverride = nil + } + /// Create an Arguments object using the given array + public init(_ array: [String]) { + self.storage = array.map { .string($0) } + self.executablePathOverride = nil + } + + #if !os(Windows) // Windows does NOT support arg0 override + /// Create an `Argument` object using the given values, but + /// override the first Argument value to `executablePathOverride`. + /// If `executablePathOverride` is nil, + /// `Arguments` will automatically use the executable path + /// as the first argument. + /// - Parameters: + /// - executablePathOverride: the value to override the first argument. + /// - remainingValues: the rest of the argument value + public init(executablePathOverride: String?, remainingValues: [String]) { + self.storage = remainingValues.map { .string($0) } + if let executablePathOverride = executablePathOverride { + self.executablePathOverride = .string(executablePathOverride) + } else { + self.executablePathOverride = nil + } + } + + /// Create an `Argument` object using the given values, but + /// override the first Argument value to `executablePathOverride`. + /// If `executablePathOverride` is nil, + /// `Arguments` will automatically use the executable path + /// as the first argument. + /// - Parameters: + /// - executablePathOverride: the value to override the first argument. + /// - remainingValues: the rest of the argument value + public init(executablePathOverride: [UInt8]?, remainingValues: [[UInt8]]) { + self.storage = remainingValues.map { .rawBytes($0) } + if let override = executablePathOverride { + self.executablePathOverride = .rawBytes(override) + } else { + self.executablePathOverride = nil + } + } + + public init(_ array: [[UInt8]]) { + self.storage = array.map { .rawBytes($0) } + self.executablePathOverride = nil + } + #endif +} + +extension Arguments: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + var result: [String] = self.storage.map(\.description) + + if let override = self.executablePathOverride { + result.insert("override\(override.description)", at: 0) + } + return result.description + } + + public var debugDescription: String { return self.description } +} + +// MARK: - Environment + +/// A set of environment variables to use when executing the subprocess. +public struct Environment: Sendable, Hashable { + internal enum Configuration: Sendable, Hashable { + case inherit([String: String]) + case custom([String: String]) + #if !os(Windows) + case rawBytes([[UInt8]]) + #endif + } + + internal let config: Configuration + + init(config: Configuration) { + self.config = config + } + /// Child process should inherit the same environment + /// values from its parent process. + public static var inherit: Self { + return .init(config: .inherit([:])) + } + /// Override the provided `newValue` in the existing `Environment` + public func updating(_ newValue: [String: String]) -> Self { + return .init(config: .inherit(newValue)) + } + /// Use custom environment variables + public static func custom(_ newValue: [String: String]) -> Self { + return .init(config: .custom(newValue)) + } + + #if !os(Windows) + /// Use custom environment variables of raw bytes + public static func custom(_ newValue: [[UInt8]]) -> Self { + return .init(config: .rawBytes(newValue)) + } + #endif +} + +extension Environment: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + switch self.config { + case .custom(let customDictionary): + return """ + Custom environment: + \(customDictionary) + """ + case .inherit(let updateValue): + return """ + Inherting current environment with updates: + \(updateValue) + """ + #if !os(Windows) + case .rawBytes(let rawBytes): + return """ + Raw bytes: + \(rawBytes) + """ + #endif + } + } + + public var debugDescription: String { + return self.description + } + + internal static func currentEnvironmentValues() -> [String: String] { + return self.withCopiedEnv { environments in + var results: [String: String] = [:] + for env in environments { + let environmentString = String(cString: env) + + #if os(Windows) + // Windows GetEnvironmentStringsW API can return + // magic environment variables set by the cmd shell + // that starts with `=` + // We should exclude these values + if environmentString.utf8.first == Character("=").utf8.first { + continue + } + #endif // os(Windows) + + guard let delimiter = environmentString.firstIndex(of: "=") else { + continue + } + + let key = String(environmentString[environmentString.startIndex.. UnsafeMutablePointer { + switch self { + case .string(let string): + return strdup(string) + case .rawBytes(let rawBytes): + return strdup(rawBytes) + } + } + + var stringValue: String? { + switch self { + case .string(let string): + return string + case .rawBytes(let rawBytes): + return String(decoding: rawBytes, as: UTF8.self) + } + } + + var description: String { + switch self { + case .string(let string): + return string + case .rawBytes(let bytes): + return bytes.description + } + } + + var count: Int { + switch self { + case .string(let string): + return string.count + case .rawBytes(let rawBytes): + return strnlen(rawBytes, Int.max) + } + } + + func hash(into hasher: inout Hasher) { + // If Raw bytes is valid UTF8, hash it as so + switch self { + case .string(let string): + hasher.combine(string) + case .rawBytes(let bytes): + if let stringValue = self.stringValue { + hasher.combine(stringValue) + } else { + hasher.combine(bytes) + } + } + } +} + +/// A simple wrapper on `FileDescriptor` plus a flag indicating +/// whether it should be closed automactially when done. +internal struct TrackedFileDescriptor: Hashable { + internal let closeWhenDone: Bool + internal let wrapped: FileDescriptor + + internal init( + _ wrapped: FileDescriptor, + closeWhenDone: Bool + ) { + self.wrapped = wrapped + self.closeWhenDone = closeWhenDone + } + + internal func safelyClose() throws { + guard self.closeWhenDone else { + return + } + + do { + try self.wrapped.close() + } catch { + guard let errno: Errno = error as? Errno else { + throw error + } + if errno != .badFileDescriptor { + throw errno + } + } + } + + internal var platformDescriptor: PlatformFileDescriptor { + return self.wrapped.platformDescriptor + } +} + +internal struct CreatedPipe { + internal let readFileDescriptor: TrackedFileDescriptor? + internal let writeFileDescriptor: TrackedFileDescriptor? + + internal init( + readFileDescriptor: TrackedFileDescriptor?, + writeFileDescriptor: TrackedFileDescriptor? + ) { + self.readFileDescriptor = readFileDescriptor + self.writeFileDescriptor = writeFileDescriptor + } + + internal init(closeWhenDone: Bool) throws { + let pipe = try FileDescriptor.pipe() + + self.readFileDescriptor = .init( + pipe.readEnd, + closeWhenDone: closeWhenDone + ) + self.writeFileDescriptor = .init( + pipe.writeEnd, + closeWhenDone: closeWhenDone + ) + } +} + +extension FilePath { + static var currentWorkingDirectory: Self { + let path = getcwd(nil, 0)! + defer { free(path) } + return .init(String(cString: path)) + } +} + +extension Optional where Wrapped: Collection { + func withOptionalUnsafeBufferPointer( + _ body: ((UnsafeBufferPointer)?) throws -> Result + ) rethrows -> Result { + switch self { + case .some(let wrapped): + guard let array: [Wrapped.Element] = wrapped as? Array else { + return try body(nil) + } + return try array.withUnsafeBufferPointer { ptr in + return try body(ptr) + } + case .none: + return try body(nil) + } + } +} + +extension Optional where Wrapped == String { + func withOptionalCString( + _ body: ((UnsafePointer)?) throws -> Result + ) rethrows -> Result { + switch self { + case .none: + return try body(nil) + case .some(let wrapped): + return try wrapped.withCString { + return try body($0) + } + } + } + + var stringValue: String { + return self ?? "nil" + } +} + +internal func withAsyncTaskCleanupHandler( + _ body: () async throws -> Result, + onCleanup handler: @Sendable @escaping () async -> Void, + isolation: isolated (any Actor)? = #isolation +) async rethrows -> Result { + return try await withThrowingTaskGroup( + of: Void.self, + returning: Result.self + ) { group in + group.addTask { + // Keep this task sleep indefinitely until the parent task is cancelled. + // `Task.sleep` throws `CancellationError` when the task is canceled + // before the time ends. We then run the cancel handler. + do { while true { try await Task.sleep(nanoseconds: 1_000_000_000) } } catch {} + // Run task cancel handler + await handler() + } + + do { + let result = try await body() + group.cancelAll() + return result + } catch { + await handler() + throw error + } + } +} diff --git a/Sources/_Subprocess/Error.swift b/Sources/_Subprocess/Error.swift new file mode 100644 index 00000000..bf1c9114 --- /dev/null +++ b/Sources/_Subprocess/Error.swift @@ -0,0 +1,141 @@ +//===----------------------------------------------------------------------===// +// +// 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(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +/// Error thrown from Subprocess +public struct SubprocessError: Swift.Error, Hashable, Sendable { + /// The error code of this error + public let code: SubprocessError.Code + /// The underlying error that caused this error, if any + public let underlyingError: UnderlyingError? +} + +// MARK: - Error Codes +extension SubprocessError { + /// A SubprocessError Code + public struct Code: Hashable, Sendable { + internal enum Storage: Hashable, Sendable { + case spawnFailed + case executableNotFound(String) + case failedToChangeWorkingDirectory(String) + case failedToReadFromSubprocess + case failedToWriteToSubprocess + case failedToMonitorProcess + // Signal + case failedToSendSignal(Int32) + // Windows Only + case failedToTerminate + case failedToSuspend + case failedToResume + case failedToCreatePipe + case invalidWindowsPath(String) + } + + public var value: Int { + switch self.storage { + case .spawnFailed: + return 0 + case .executableNotFound(_): + return 1 + case .failedToChangeWorkingDirectory(_): + return 2 + case .failedToReadFromSubprocess: + return 3 + case .failedToWriteToSubprocess: + return 4 + case .failedToMonitorProcess: + return 5 + case .failedToSendSignal(_): + return 6 + case .failedToTerminate: + return 7 + case .failedToSuspend: + return 8 + case .failedToResume: + return 9 + case .failedToCreatePipe: + return 10 + case .invalidWindowsPath(_): + return 11 + } + } + + internal let storage: Storage + + internal init(_ storage: Storage) { + self.storage = storage + } + } +} + +// MARK: - Description +extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + switch self.code.storage { + case .spawnFailed: + return "Failed to spawn the new process." + case .executableNotFound(let executableName): + return "Executable \"\(executableName)\" is not found or cannot be executed." + case .failedToChangeWorkingDirectory(let workingDirectory): + return "Failed to set working directory to \"\(workingDirectory)\"." + case .failedToReadFromSubprocess: + return "Failed to read bytes from the child process with underlying error: \(self.underlyingError!)" + case .failedToWriteToSubprocess: + return "Failed to write bytes to the child process." + case .failedToMonitorProcess: + return "Failed to monitor the state of child process with underlying error: \(self.underlyingError!)" + case .failedToSendSignal(let signal): + return "Failed to send signal \(signal) to the child process." + case .failedToTerminate: + return "Failed to terminate the child process." + case .failedToSuspend: + return "Failed to suspend the child process." + case .failedToResume: + return "Failed to resume the child process." + case .failedToCreatePipe: + return "Failed to create a pipe to communicate to child process." + case .invalidWindowsPath(let badPath): + return "\"\(badPath)\" is not a valid Windows path." + } + } + + public var debugDescription: String { self.description } +} + +extension SubprocessError { + /// The underlying error that caused this SubprocessError. + /// - On Unix-like systems, `UnderlyingError` wraps `errno` from libc; + /// - On Windows, `UnderlyingError` wraps Windows Error code + public struct UnderlyingError: Swift.Error, RawRepresentable, Hashable, Sendable { + #if os(Windows) + public typealias RawValue = DWORD + #else + public typealias RawValue = Int32 + #endif + + public let rawValue: RawValue + + public init(rawValue: RawValue) { + self.rawValue = rawValue + } + } +} diff --git a/Sources/_Subprocess/Execution.swift b/Sources/_Subprocess/Execution.swift new file mode 100644 index 00000000..8da9b492 --- /dev/null +++ b/Sources/_Subprocess/Execution.swift @@ -0,0 +1,192 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +import Synchronization + +/// An object that repersents a subprocess that has been +/// executed. You can use this object to send signals to the +/// child process as well as stream its output and error. +@available(macOS 15.0, *) // FIXME: manually added availability +public final class Execution< + Output: OutputProtocol, + Error: OutputProtocol +>: Sendable { + /// The process identifier of the current execution + public let processIdentifier: ProcessIdentifier + + internal let output: Output + internal let error: Error + internal let outputPipe: CreatedPipe + internal let errorPipe: CreatedPipe + internal let outputConsumptionState: Atomic + #if os(Windows) + internal let consoleBehavior: PlatformOptions.ConsoleBehavior + + init( + processIdentifier: ProcessIdentifier, + output: Output, + error: Error, + outputPipe: CreatedPipe, + errorPipe: CreatedPipe, + consoleBehavior: PlatformOptions.ConsoleBehavior + ) { + self.processIdentifier = processIdentifier + self.output = output + self.error = error + self.outputPipe = outputPipe + self.errorPipe = errorPipe + self.outputConsumptionState = Atomic(0) + self.consoleBehavior = consoleBehavior + } + #else + init( + processIdentifier: ProcessIdentifier, + output: Output, + error: Error, + outputPipe: CreatedPipe, + errorPipe: CreatedPipe + ) { + self.processIdentifier = processIdentifier + self.output = output + self.error = error + self.outputPipe = outputPipe + self.errorPipe = errorPipe + self.outputConsumptionState = Atomic(0) + } + #endif // os(Windows) +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution where Output == SequenceOutput { + /// The standard output of the subprocess. + /// + /// Accessing this property will **fatalError** if this property was + /// accessed multiple times. Subprocess communicates with parent process + /// via pipe under the hood and each pipe can only be consumed once. + @available(macOS 15.0, *) // FIXME: manually added availability + public var standardOutput: some AsyncSequence { + let consumptionState = self.outputConsumptionState.bitwiseXor( + OutputConsumptionState.standardOutputConsumed.rawValue, + ordering: .relaxed + ).newValue + + guard OutputConsumptionState(rawValue: consumptionState).contains(.standardOutputConsumed), + let fd = self.outputPipe.readFileDescriptor + else { + fatalError("The standard output has already been consumed") + } + return AsyncBufferSequence(fileDescriptor: fd) + } +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution where Error == SequenceOutput { + /// The standard error of the subprocess. + /// + /// Accessing this property will **fatalError** if this property was + /// accessed multiple times. Subprocess communicates with parent process + /// via pipe under the hood and each pipe can only be consumed once. + @available(macOS 15.0, *) // FIXME: manually added availability + public var standardError: some AsyncSequence { + let consumptionState = self.outputConsumptionState.bitwiseXor( + OutputConsumptionState.standardErrorConsumed.rawValue, + ordering: .relaxed + ).newValue + + guard OutputConsumptionState(rawValue: consumptionState).contains(.standardErrorConsumed), + let fd = self.errorPipe.readFileDescriptor + else { + fatalError("The standard output has already been consumed") + } + return AsyncBufferSequence(fileDescriptor: fd) + } +} + +// MARK: - Output Capture +internal enum OutputCapturingState: Sendable { + case standardOutputCaptured(Output) + case standardErrorCaptured(Error) +} + +internal struct OutputConsumptionState: OptionSet { + typealias RawValue = UInt8 + + internal let rawValue: UInt8 + + internal init(rawValue: UInt8) { + self.rawValue = rawValue + } + + static let standardOutputConsumed: Self = .init(rawValue: 0b0001) + static let standardErrorConsumed: Self = .init(rawValue: 0b0010) +} + +internal typealias CapturedIOs< + Output: Sendable, + Error: Sendable +> = (standardOutput: Output, standardError: Error) + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { + internal func captureIOs() async throws -> CapturedIOs< + Output.OutputType, Error.OutputType + > { + return try await withThrowingTaskGroup( + of: OutputCapturingState.self + ) { group in + group.addTask { + let stdout = try await self.output.captureOutput( + from: self.outputPipe.readFileDescriptor + ) + return .standardOutputCaptured(stdout) + } + group.addTask { + let stderr = try await self.error.captureOutput( + from: self.errorPipe.readFileDescriptor + ) + return .standardErrorCaptured(stderr) + } + + var stdout: Output.OutputType! + var stderror: Error.OutputType! + while let state = try await group.next() { + switch state { + case .standardOutputCaptured(let output): + stdout = output + case .standardErrorCaptured(let error): + stderror = error + } + } + return ( + standardOutput: stdout, + standardError: stderror + ) + } + } +} diff --git a/Sources/_Subprocess/IO/Input.swift b/Sources/_Subprocess/IO/Input.swift new file mode 100644 index 00000000..5aad5d94 --- /dev/null +++ b/Sources/_Subprocess/IO/Input.swift @@ -0,0 +1,315 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#if SubprocessFoundation + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif + +#endif // SubprocessFoundation + +// MARK: - Input + +/// `InputProtocol` defines the `write(with:)` method that a type +/// must implement to serve as the input source for a subprocess. +public protocol InputProtocol: Sendable, ~Copyable { + /// Asynchronously write the input to the subprocess using the + /// write file descriptor + func write(with writer: StandardInputWriter) async throws +} + +/// A concrete `Input` type for subprocesses that indicates +/// the absence of input to the subprocess. On Unix-like systems, +/// `NoInput` redirects the standard input of the subprocess +/// to `/dev/null`, while on Windows, it does not bind any +/// file handle to the subprocess standard input handle. +public struct NoInput: InputProtocol { + internal func createPipe() throws -> CreatedPipe { + #if os(Windows) + // On Windows, instead of binding to dev null, + // we don't set the input handle in the `STARTUPINFOW` + // to signal no input + return CreatedPipe( + readFileDescriptor: nil, + writeFileDescriptor: nil + ) + #else + let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) + return CreatedPipe( + readFileDescriptor: .init(devnull, closeWhenDone: true), + writeFileDescriptor: nil + ) + #endif + } + + public func write(with writer: StandardInputWriter) async throws { + // noop + } + + internal init() {} +} + +/// A concrete `Input` type for subprocesses that +/// reads input from a specified `FileDescriptor`. +/// Developers have the option to instruct the `Subprocess` to +/// automatically close the provided `FileDescriptor` +/// after the subprocess is spawned. +public struct FileDescriptorInput: InputProtocol { + private let fileDescriptor: FileDescriptor + private let closeAfterSpawningProcess: Bool + + internal func createPipe() throws -> CreatedPipe { + return CreatedPipe( + readFileDescriptor: .init( + self.fileDescriptor, + closeWhenDone: self.closeAfterSpawningProcess + ), + writeFileDescriptor: nil + ) + } + + public func write(with writer: StandardInputWriter) async throws { + // noop + } + + internal init( + fileDescriptor: FileDescriptor, + closeAfterSpawningProcess: Bool + ) { + self.fileDescriptor = fileDescriptor + self.closeAfterSpawningProcess = closeAfterSpawningProcess + } +} + +/// A concrete `Input` type for subprocesses that reads input +/// from a given type conforming to `StringProtocol`. +/// Developers can specify the string encoding to use when +/// encoding the string to data, which defaults to UTF-8. +public struct StringInput< + InputString: StringProtocol & Sendable, + Encoding: Unicode.Encoding +>: InputProtocol { + private let string: InputString + + public func write(with writer: StandardInputWriter) async throws { + guard let array = self.string.byteArray(using: Encoding.self) else { + return + } + _ = try await writer.write(array) + } + + internal init(string: InputString, encoding: Encoding.Type) { + self.string = string + } +} + +/// A concrete `Input` type for subprocesses that reads input +/// from a given `UInt8` Array. +public struct ArrayInput: InputProtocol { + private let array: [UInt8] + + public func write(with writer: StandardInputWriter) async throws { + _ = try await writer.write(self.array) + } + + internal init(array: [UInt8]) { + self.array = array + } +} + +/// A concrete `Input` type for subprocess that indicates that +/// the Subprocess should read its input from `StandardInputWriter`. +public struct CustomWriteInput: InputProtocol { + public func write(with writer: StandardInputWriter) async throws { + // noop + } + + internal init() {} +} + +extension InputProtocol where Self == NoInput { + /// Create a Subprocess input that specfies there is no input + public static var none: Self { .init() } +} + +extension InputProtocol where Self == FileDescriptorInput { + /// Create a Subprocess input from a `FileDescriptor` and + /// specify whether the `FileDescriptor` should be closed + /// after the process is spawned. + public static func fileDescriptor( + _ fd: FileDescriptor, + closeAfterSpawningProcess: Bool + ) -> Self { + return .init( + fileDescriptor: fd, + closeAfterSpawningProcess: closeAfterSpawningProcess + ) + } +} + +extension InputProtocol { + /// Create a Subprocess input from a `Array` of `UInt8`. + public static func array( + _ array: [UInt8] + ) -> Self where Self == ArrayInput { + return ArrayInput(array: array) + } + + /// Create a Subprocess input from a type that conforms to `StringProtocol` + public static func string< + InputString: StringProtocol & Sendable + >( + _ string: InputString + ) -> Self where Self == StringInput { + return .init(string: string, encoding: UTF8.self) + } + + /// Create a Subprocess input from a type that conforms to `StringProtocol` + public static func string< + InputString: StringProtocol & Sendable, + Encoding: Unicode.Encoding + >( + _ string: InputString, + using encoding: Encoding.Type + ) -> Self where Self == StringInput { + return .init(string: string, encoding: encoding) + } +} + +extension InputProtocol { + internal func createPipe() throws -> CreatedPipe { + if let noInput = self as? NoInput { + return try noInput.createPipe() + } else if let fdInput = self as? FileDescriptorInput { + return try fdInput.createPipe() + } + // Base implementation + return try CreatedPipe(closeWhenDone: true) + } +} + +// MARK: - StandardInputWriter + +/// A writer that writes to the standard input of the subprocess. +public final actor StandardInputWriter: Sendable { + + internal let fileDescriptor: TrackedFileDescriptor + + init(fileDescriptor: TrackedFileDescriptor) { + self.fileDescriptor = fileDescriptor + } + + /// Write an array of UInt8 to the standard input of the subprocess. + /// - Parameter array: The sequence of bytes to write. + /// - Returns number of bytes written. + public func write( + _ array: [UInt8] + ) async throws -> Int { + return try await self.fileDescriptor.wrapped.write(array) + } + + /// Write a StringProtocol to the standard input of the subprocess. + /// - Parameters: + /// - string: The string to write. + /// - encoding: The encoding to use when converting string to bytes + /// - Returns number of bytes written. + public func write( + _ string: some StringProtocol, + using encoding: Encoding.Type = UTF8.self + ) async throws -> Int { + if let array = string.byteArray(using: encoding) { + return try await self.write(array) + } + return 0 + } + + /// Signal all writes are finished + public func finish() async throws { + try self.fileDescriptor.safelyClose() + } +} + +extension StringProtocol { + #if SubprocessFoundation + private func convertEncoding( + _ encoding: Encoding.Type + ) -> String.Encoding? { + switch encoding { + case is UTF8.Type: + return .utf8 + case is UTF16.Type: + return .utf16 + case is UTF32.Type: + return .utf32 + default: + return nil + } + } + #endif + package func byteArray(using encoding: Encoding.Type) -> [UInt8]? { + if Encoding.self == Unicode.ASCII.self { + let isASCII = self.utf8.allSatisfy { + return Character(Unicode.Scalar($0)).isASCII + } + + guard isASCII else { + return nil + } + return Array(self.utf8) + } + if Encoding.self == UTF8.self { + return Array(self.utf8) + } + if Encoding.self == UTF16.self { + return Array(self.utf16).flatMap { input in + var uint16: UInt16 = input + return withUnsafeBytes(of: &uint16) { ptr in + Array(ptr) + } + } + } + #if SubprocessFoundation + if let stringEncoding = self.convertEncoding(encoding), + let encoded = self.data(using: stringEncoding) + { + return Array(encoded) + } + return nil + #else + return nil + #endif + } +} + +extension String { + package init( + decodingBytes bytes: [T], + as encoding: Encoding.Type + ) { + self = bytes.withUnsafeBytes { raw in + String( + decoding: raw.bindMemory(to: Encoding.CodeUnit.self).lazy.map { $0 }, + as: encoding + ) + } + } +} diff --git a/Sources/_Subprocess/IO/Output.swift b/Sources/_Subprocess/IO/Output.swift new file mode 100644 index 00000000..be186dd6 --- /dev/null +++ b/Sources/_Subprocess/IO/Output.swift @@ -0,0 +1,298 @@ +//===----------------------------------------------------------------------===// +// +// 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 +internal import Dispatch + +// MARK: - Output + +/// `OutputProtocol` specifies the set of methods that a type +/// must implement to serve as the output target for a subprocess. +/// Instead of developing custom implementations of `OutputProtocol`, +/// it is recommended to utilize the default implementations provided +/// by the `Subprocess` library to specify the output handling requirements. +public protocol OutputProtocol: Sendable, ~Copyable { + associatedtype OutputType: Sendable + + /// Convert the output from buffer to expected output type + func output(from buffer: some Sequence) throws -> OutputType + + /// The max amount of data to collect for this output. + var maxSize: Int { get } +} + +extension OutputProtocol { + /// The max amount of data to collect for this output. + public var maxSize: Int { 128 * 1024 } +} + +/// A concrete `Output` type for subprocesses that indicates that +/// the `Subprocess` should not collect or redirect output +/// from the child process. On Unix-like systems, `DiscardedOutput` +/// redirects the standard output of the subprocess to `/dev/null`, +/// while on Windows, it does not bind any file handle to the +/// subprocess standard output handle. +public struct DiscardedOutput: OutputProtocol { + public typealias OutputType = Void + + internal func createPipe() throws -> CreatedPipe { + #if os(Windows) + // On Windows, instead of binding to dev null, + // we don't set the input handle in the `STARTUPINFOW` + // to signal no output + return CreatedPipe( + readFileDescriptor: nil, + writeFileDescriptor: nil + ) + #else + let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) + return CreatedPipe( + readFileDescriptor: .init(devnull, closeWhenDone: true), + writeFileDescriptor: nil + ) + #endif + } + + internal init() {} +} + +/// A concrete `Output` type for subprocesses that +/// writes output to a specified `FileDescriptor`. +/// Developers have the option to instruct the `Subprocess` to +/// automatically close the provided `FileDescriptor` +/// after the subprocess is spawned. +public struct FileDescriptorOutput: OutputProtocol { + public typealias OutputType = Void + + private let closeAfterSpawningProcess: Bool + private let fileDescriptor: FileDescriptor + + internal func createPipe() throws -> CreatedPipe { + return CreatedPipe( + readFileDescriptor: nil, + writeFileDescriptor: .init( + self.fileDescriptor, + closeWhenDone: self.closeAfterSpawningProcess + ) + ) + } + + internal init( + fileDescriptor: FileDescriptor, + closeAfterSpawningProcess: Bool + ) { + self.fileDescriptor = fileDescriptor + self.closeAfterSpawningProcess = closeAfterSpawningProcess + } +} + +/// A concrete `Output` type for subprocesses that collects output +/// from the subprocess as `String` with the given encoding. +/// This option must be used with he `run()` method that +/// returns a `CollectedResult`. +public struct StringOutput: OutputProtocol { + public typealias OutputType = String? + public let maxSize: Int + private let encoding: Encoding.Type + + public func output(from buffer: some Sequence) throws -> String? { + // FIXME: Span to String + let array = Array(buffer) + return String(decodingBytes: array, as: Encoding.self) + } + + internal init(limit: Int, encoding: Encoding.Type) { + self.maxSize = limit + self.encoding = encoding + } +} + +/// A concrete `Output` type for subprocesses that collects output +/// from the subprocess as `[UInt8]`. This option must be used with +/// the `run()` method that returns a `CollectedResult` +public struct BytesOutput: OutputProtocol { + public typealias OutputType = [UInt8] + public let maxSize: Int + + internal func captureOutput(from fileDescriptor: TrackedFileDescriptor?) async throws -> [UInt8] { + return try await withCheckedThrowingContinuation { continuation in + guard let fileDescriptor = fileDescriptor else { + // Show not happen due to type system constraints + fatalError("Trying to capture output without file descriptor") + } + fileDescriptor.wrapped.readUntilEOF(upToLength: self.maxSize) { result in + switch result { + case .success(let data): + // FIXME: remove workaround for + // rdar://143992296 + // https://github.com/swiftlang/swift-subprocess/issues/3 + #if os(Windows) + continuation.resume(returning: data) + #else + continuation.resume(returning: data.array()) + #endif + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + public func output(from buffer: some Sequence) throws -> [UInt8] { + fatalError("Not implemented") + } + + internal init(limit: Int) { + self.maxSize = limit + } +} + +/// A concrete `Output` type for subprocesses that redirects +/// the child output to the `.standardOutput` (a sequence) or `.standardError` +/// property of `Execution`. This output type is +/// only applicable to the `run()` family that takes a custom closure. +public struct SequenceOutput: OutputProtocol { + public typealias OutputType = Void + + internal init() {} +} + +extension OutputProtocol where Self == DiscardedOutput { + /// Create a Subprocess output that discards the output + public static var discarded: Self { .init() } +} + +extension OutputProtocol where Self == FileDescriptorOutput { + /// Create a Subprocess output that writes output to a `FileDescriptor` + /// and optionally close the `FileDescriptor` once process spawned. + public static func fileDescriptor( + _ fd: FileDescriptor, + closeAfterSpawningProcess: Bool + ) -> Self { + return .init(fileDescriptor: fd, closeAfterSpawningProcess: closeAfterSpawningProcess) + } +} + +extension OutputProtocol where Self == StringOutput { + /// Create a `Subprocess` output that collects output as + /// UTF8 String with 128kb limit. + public static var string: Self { + .init(limit: 128 * 1024, encoding: UTF8.self) + } +} + +extension OutputProtocol { + /// Create a `Subprocess` output that collects output as + /// `String` using the given encoding up to limit it bytes. + public static func string( + limit: Int, + encoding: Encoding.Type + ) -> Self where Self == StringOutput { + return .init(limit: limit, encoding: encoding) + } +} + +extension OutputProtocol where Self == BytesOutput { + /// Create a `Subprocess` output that collects output as + /// `Buffer` with 128kb limit. + public static var bytes: Self { .init(limit: 128 * 1024) } + + /// Create a `Subprocess` output that collects output as + /// `Buffer` up to limit it bytes. + public static func bytes(limit: Int) -> Self { + return .init(limit: limit) + } +} + +extension OutputProtocol where Self == SequenceOutput { + /// Create a `Subprocess` output that redirects the output + /// to the `.standardOutput` (or `.standardError`) property + /// of `Execution` as `AsyncSequence`. + public static var sequence: Self { .init() } +} + +// MARK: - Default Implementations +extension OutputProtocol { + @_disfavoredOverload + internal func createPipe() throws -> CreatedPipe { + if let discard = self as? DiscardedOutput { + return try discard.createPipe() + } else if let fdOutput = self as? FileDescriptorOutput { + return try fdOutput.createPipe() + } + // Base pipe based implementation for everything else + return try CreatedPipe(closeWhenDone: true) + } + + /// Capture the output from the subprocess up to maxSize + @_disfavoredOverload + internal func captureOutput( + from fileDescriptor: TrackedFileDescriptor? + ) async throws -> OutputType { + if let bytesOutput = self as? BytesOutput { + return try await bytesOutput.captureOutput(from: fileDescriptor) as! Self.OutputType + } + return try await withCheckedThrowingContinuation { continuation in + if OutputType.self == Void.self { + continuation.resume(returning: () as! OutputType) + return + } + guard let fileDescriptor = fileDescriptor else { + // Show not happen due to type system constraints + fatalError("Trying to capture output without file descriptor") + } + + fileDescriptor.wrapped.readUntilEOF(upToLength: self.maxSize) { result in + do { + switch result { + case .success(let data): + // FIXME: remove workaround for + // rdar://143992296 + // https://github.com/swiftlang/swift-subprocess/issues/3 + let output = try self.output(from: data) + continuation.resume(returning: output) + case .failure(let error): + continuation.resume(throwing: error) + } + } catch { + continuation.resume(throwing: error) + } + } + } + } +} + +extension OutputProtocol where OutputType == Void { + internal func captureOutput(from fileDescriptor: TrackedFileDescriptor?) async throws {} + + public func output(from buffer: some Sequence) throws { + // noop + } +} + +extension DispatchData { + internal func array() -> [UInt8] { + var result: [UInt8]? + self.enumerateBytes { buffer, byteIndex, stop in + let currentChunk = Array(UnsafeRawBufferPointer(buffer)) + if result == nil { + result = currentChunk + } else { + result?.append(contentsOf: currentChunk) + } + } + return result ?? [] + } +} diff --git a/Sources/_Subprocess/LockedState.swift b/Sources/_Subprocess/LockedState.swift deleted file mode 100644 index e095668c..00000000 --- a/Sources/_Subprocess/LockedState.swift +++ /dev/null @@ -1,160 +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 canImport(os) -internal import os -#if FOUNDATION_FRAMEWORK && canImport(C.os.lock) -internal import C.os.lock -#endif -#elseif canImport(Bionic) -import Bionic -#elseif canImport(Glibc) -import Glibc -#elseif canImport(Musl) -import Musl -#elseif canImport(WinSDK) -import WinSDK -#endif - -package struct LockedState { - - // Internal implementation for a cheap lock to aid sharing code across platforms - private struct _Lock { -#if canImport(os) - typealias Primitive = os_unfair_lock -#elseif canImport(Bionic) || canImport(Glibc) || canImport(Musl) - typealias Primitive = pthread_mutex_t -#elseif canImport(WinSDK) - typealias Primitive = SRWLOCK -#elseif os(WASI) - // WASI is single-threaded, so we don't need a lock. - typealias Primitive = Void -#endif - - typealias PlatformLock = UnsafeMutablePointer - var _platformLock: PlatformLock - - fileprivate static func initialize(_ platformLock: PlatformLock) { -#if canImport(os) - platformLock.initialize(to: os_unfair_lock()) -#elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_init(platformLock, nil) -#elseif canImport(WinSDK) - InitializeSRWLock(platformLock) -#elseif os(WASI) - // no-op -#endif - } - - fileprivate static func deinitialize(_ platformLock: PlatformLock) { -#if canImport(Bionic) || canImport(Glibc) - pthread_mutex_destroy(platformLock) -#endif - platformLock.deinitialize(count: 1) - } - - static fileprivate func lock(_ platformLock: PlatformLock) { -#if canImport(os) - os_unfair_lock_lock(platformLock) -#elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_lock(platformLock) -#elseif canImport(WinSDK) - AcquireSRWLockExclusive(platformLock) -#elseif os(WASI) - // no-op -#endif - } - - static fileprivate func unlock(_ platformLock: PlatformLock) { -#if canImport(os) - os_unfair_lock_unlock(platformLock) -#elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_unlock(platformLock) -#elseif canImport(WinSDK) - ReleaseSRWLockExclusive(platformLock) -#elseif os(WASI) - // no-op -#endif - } - } - - private class _Buffer: ManagedBuffer { - deinit { - withUnsafeMutablePointerToElements { - _Lock.deinitialize($0) - } - } - } - - private let _buffer: ManagedBuffer - - package init(initialState: State) { - _buffer = _Buffer.create(minimumCapacity: 1, makingHeaderWith: { buf in - buf.withUnsafeMutablePointerToElements { - _Lock.initialize($0) - } - return initialState - }) - } - - package func withLock(_ body: @Sendable (inout State) throws -> T) rethrows -> T { - try withLockUnchecked(body) - } - - package func withLockUnchecked(_ body: (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } - } - - // Ensures the managed state outlives the locked scope. - package func withLockExtendingLifetimeOfState(_ body: @Sendable (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - return try withExtendedLifetime(state.pointee) { - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } - } - } -} - -extension LockedState where State == Void { - package init() { - self.init(initialState: ()) - } - - package func withLock(_ body: @Sendable () throws -> R) rethrows -> R { - return try withLock { _ in - try body() - } - } - - package func lock() { - _buffer.withUnsafeMutablePointerToElements { lock in - _Lock.lock(lock) - } - } - - package func unlock() { - _buffer.withUnsafeMutablePointerToElements { lock in - _Lock.unlock(lock) - } - } -} - -extension LockedState: @unchecked Sendable where State: Sendable {} - diff --git a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift index 4ac2276e..cd5c310a 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift @@ -2,306 +2,144 @@ // // 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 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// #if canImport(Darwin) -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - import Darwin -import Dispatch -import SystemPackage - -#if FOUNDATION_FRAMEWORK -@_implementationOnly import _FoundationCShims +internal import Dispatch +#if canImport(System) +import System #else -import _CShims +@preconcurrency import SystemPackage #endif -// Darwin specific implementation -extension Subprocess.Configuration { - internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes +import _SubprocessCShims - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { - let (executablePath, - env, argv, - intendedWorkingDir, - uidPtr, gidPtr, supplementaryGroups - ) = try self.preSpawn() - defer { - for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - uidPtr?.deallocate() - gidPtr?.deallocate() - } +#if SubprocessFoundation - // Setup file actions and spawn attributes - var fileActions: posix_spawn_file_actions_t? = nil - var spawnAttributes: posix_spawnattr_t? = nil - // Setup stdin, stdout, and stderr - posix_spawn_file_actions_init(&fileActions) - defer { - posix_spawn_file_actions_destroy(&fileActions) - } - // Input - var result: Int32 = -1 - if let inputRead = input.getReadFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, inputRead.rawValue, 0) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let inputWrite = input.getWriteFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, inputWrite.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Output - if let outputWrite = output.getWriteFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, outputWrite.rawValue, 1) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let outputRead = output.getReadFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, outputRead.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Error - if let errorWrite = error.getWriteFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, errorWrite.rawValue, 2) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let errorRead = error.getReadFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, errorRead.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Setup spawnAttributes - posix_spawnattr_init(&spawnAttributes) - defer { - posix_spawnattr_destroy(&spawnAttributes) - } - var noSignals = sigset_t() - var allSignals = sigset_t() - sigemptyset(&noSignals) - sigfillset(&allSignals) - posix_spawnattr_setsigmask(&spawnAttributes, &noSignals) - posix_spawnattr_setsigdefault(&spawnAttributes, &allSignals) - // Configure spawnattr - var spawnAttributeError: Int32 = 0 - var flags: Int32 = POSIX_SPAWN_CLOEXEC_DEFAULT | - POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF - if let pgid = self.platformOptions.processGroupID { - flags |= POSIX_SPAWN_SETPGROUP - spawnAttributeError = posix_spawnattr_setpgroup(&spawnAttributes, pid_t(pgid)) - } - spawnAttributeError = posix_spawnattr_setflags(&spawnAttributes, Int16(flags)) - // Set QualityOfService - // spanattr_qos seems to only accept `QOS_CLASS_UTILITY` or `QOS_CLASS_BACKGROUND` - // and returns an error of `EINVAL` if anything else is provided - if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .utility{ - spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_UTILITY) - } else if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .background { - spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_BACKGROUND) - } - - // Setup cwd - var chdirError: Int32 = 0 - if intendedWorkingDir != .currentWorkingDirectory { - chdirError = intendedWorkingDir.withPlatformString { workDir in - return posix_spawn_file_actions_addchdir_np(&fileActions, workDir) - } - } - - // Error handling - if chdirError != 0 || spawnAttributeError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - if spawnAttributeError != 0 { - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif - if chdirError != 0 { - throw CocoaError(.fileNoSuchFile, userInfo: [ - .debugDescriptionErrorKey: "Cannot failed to change the working directory to \(intendedWorkingDir) with errno \(chdirError)" - ]) - } - } - // Run additional config - if let spawnConfig = self.platformOptions.preSpawnProcessConfigurator { - try spawnConfig(&spawnAttributes, &fileActions) - } - // Spawn - var pid: pid_t = 0 - let spawnError: CInt = executablePath.withCString { exePath in - return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in - return _subprocess_spawn( - &pid, exePath, - &fileActions, &spawnAttributes, - argv, env, - uidPtr, gidPtr, - Int32(supplementaryGroups?.count ?? 0), sgroups?.baseAddress, - self.platformOptions.createSession ? 1 : 0 - ) - } - } - // Spawn error - if spawnError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) - } - return Subprocess( - processIdentifier: .init(value: pid), - executionInput: input, - executionOutput: output, - executionError: error - ) - } -} +#endif // SubprocessFoundation -// Special keys used in Error's user dictionary -extension String { - static let debugDescriptionErrorKey = "NSDebugDescription" -} +// MARK: - PlatformOptions -// MARK: - Platform Specific Options -extension Subprocess { - /// The collection of platform-specific settings - /// to configure the subprocess when running - public struct PlatformOptions: Sendable { - public var qualityOfService: QualityOfService = .default - /// Set user ID for the subprocess - public var userID: uid_t? = nil - /// Set the real and effective group ID and the saved - /// set-group-ID of the subprocess, equivalent to calling - /// `setgid()` on the child process. - /// Group ID is used to control permissions, particularly - /// for file access. - public var groupID: gid_t? = nil - /// Set list of supplementary group IDs for the subprocess - public var supplementaryGroups: [gid_t]? = nil - /// Set the process group for the subprocess, equivalent to - /// calling `setpgid()` on the child process. - /// Process group ID is used to group related processes for - /// controlling signals. - public var processGroupID: pid_t? = nil - /// Creates a session and sets the process group ID - /// i.e. Detach from the terminal. - public var createSession: Bool = false - /// A lightweight code requirement that you use to - /// evaluate the executable for a launching process. - public var launchRequirementData: Data? = nil - /// An ordered list of steps in order to tear down the child - /// process in case the parent task is cancelled before - /// the child proces terminates. - /// Always ends in sending a `.kill` signal at the end. - public var teardownSequence: [TeardownStep] = [] - /// A closure to configure platform-specific - /// spawning constructs. This closure enables direct - /// configuration or override of underlying platform-specific - /// spawn settings that `Subprocess` utilizes internally, - /// in cases where Subprocess does not provide higher-level - /// APIs for such modifications. - /// - /// On Darwin, Subprocess uses `posix_spawn()` as the - /// underlying spawning mechanism. This closure allows - /// modification of the `posix_spawnattr_t` spawn attribute - /// and file actions `posix_spawn_file_actions_t` before - /// they are sent to `posix_spawn()`. - public var preSpawnProcessConfigurator: ( +/// The collection of platform-specific settings +/// to configure the subprocess when running +public struct PlatformOptions: Sendable { + public var qualityOfService: QualityOfService = .default + /// Set user ID for the subprocess + public var userID: uid_t? = nil + /// Set the real and effective group ID and the saved + /// set-group-ID of the subprocess, equivalent to calling + /// `setgid()` on the child process. + /// Group ID is used to control permissions, particularly + /// for file access. + public var groupID: gid_t? = nil + /// Set list of supplementary group IDs for the subprocess + public var supplementaryGroups: [gid_t]? = nil + /// Set the process group for the subprocess, equivalent to + /// calling `setpgid()` on the child process. + /// Process group ID is used to group related processes for + /// controlling signals. + public var processGroupID: pid_t? = nil + /// Creates a session and sets the process group ID + /// i.e. Detach from the terminal. + public var createSession: Bool = false + /// An ordered list of steps in order to tear down the child + /// process in case the parent task is cancelled before + /// the child proces terminates. + /// Always ends in sending a `.kill` signal at the end. + public var teardownSequence: [TeardownStep] = [] + /// A closure to configure platform-specific + /// spawning constructs. This closure enables direct + /// configuration or override of underlying platform-specific + /// spawn settings that `Subprocess` utilizes internally, + /// in cases where Subprocess does not provide higher-level + /// APIs for such modifications. + /// + /// On Darwin, Subprocess uses `posix_spawn()` as the + /// underlying spawning mechanism. This closure allows + /// modification of the `posix_spawnattr_t` spawn attribute + /// and file actions `posix_spawn_file_actions_t` before + /// they are sent to `posix_spawn()`. + public var preSpawnProcessConfigurator: + ( @Sendable ( inout posix_spawnattr_t?, inout posix_spawn_file_actions_t? ) throws -> Void )? = nil - public init() {} - } + public init() {} } -extension Subprocess.PlatformOptions: Hashable { - public static func == (lhs: Subprocess.PlatformOptions, rhs: Subprocess.PlatformOptions) -> Bool { - // Since we can't compare closure equality, - // as long as preSpawnProcessConfigurator is set - // always returns false so that `PlatformOptions` - // with it set will never equal to each other - if lhs.preSpawnProcessConfigurator != nil || - rhs.preSpawnProcessConfigurator != nil { - return false - } - return lhs.qualityOfService == rhs.qualityOfService && - lhs.userID == rhs.userID && - lhs.groupID == rhs.groupID && - lhs.supplementaryGroups == rhs.supplementaryGroups && - lhs.processGroupID == rhs.processGroupID && - lhs.createSession == rhs.createSession && - lhs.launchRequirementData == rhs.launchRequirementData - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(self.qualityOfService) - hasher.combine(self.userID) - hasher.combine(self.groupID) - hasher.combine(self.supplementaryGroups) - hasher.combine(self.processGroupID) - hasher.combine(self.createSession) - hasher.combine(self.launchRequirementData) - // Since we can't really hash closures, - // use an UUID such that as long as - // `preSpawnProcessConfigurator` is set, it will - // never equal to other PlatformOptions - if self.preSpawnProcessConfigurator != nil { - hasher.combine(UUID()) - } +extension PlatformOptions { + #if SubprocessFoundation + public typealias QualityOfService = Foundation.QualityOfService + #else + /// Constants that indicate the nature and importance of work to the system. + /// + /// Work with higher quality of service classes receive more resources + /// than work with lower quality of service classes whenever + /// there’s resource contention. + public enum QualityOfService: Int, Sendable { + /// Used for work directly involved in providing an + /// interactive UI. For example, processing control + /// events or drawing to the screen. + case userInteractive = 0x21 + /// Used for performing work that has been explicitly requested + /// by the user, and for which results must be immediately + /// presented in order to allow for further user interaction. + /// For example, loading an email after a user has selected + /// it in a message list. + case userInitiated = 0x19 + /// Used for performing work which the user is unlikely to be + /// immediately waiting for the results. This work may have been + /// requested by the user or initiated automatically, and often + /// operates at user-visible timescales using a non-modal + /// progress indicator. For example, periodic content updates + /// or bulk file operations, such as media import. + case utility = 0x11 + /// Used for work that is not user initiated or visible. + /// In general, a user is unaware that this work is even happening. + /// For example, pre-fetching content, search indexing, backups, + /// or syncing of data with external systems. + case background = 0x09 + /// Indicates no explicit quality of service information. + /// Whenever possible, an appropriate quality of service is determined + /// from available sources. Otherwise, some quality of service level + /// between `.userInteractive` and `.utility` is used. + case `default` = -1 } + #endif } -extension Subprocess.PlatformOptions : CustomStringConvertible, CustomDebugStringConvertible { +extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible { internal func description(withIndent indent: Int) -> String { let indent = String(repeating: " ", count: indent * 4) return """ -PlatformOptions( -\(indent) qualityOfService: \(self.qualityOfService), -\(indent) userID: \(String(describing: userID)), -\(indent) groupID: \(String(describing: groupID)), -\(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), -\(indent) processGroupID: \(String(describing: processGroupID)), -\(indent) createSession: \(createSession), -\(indent) launchRequirementData: \(String(describing: launchRequirementData)), -\(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") -\(indent)) -""" + PlatformOptions( + \(indent) qualityOfService: \(self.qualityOfService), + \(indent) userID: \(String(describing: userID)), + \(indent) groupID: \(String(describing: groupID)), + \(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), + \(indent) processGroupID: \(String(describing: processGroupID)), + \(indent) createSession: \(createSession), + \(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") + \(indent)) + """ } public var description: String { @@ -313,11 +151,243 @@ PlatformOptions( } } +// MARK: - Spawn +extension Configuration { + @available(macOS 15.0, *) // FIXME: manually added availability + internal func spawn< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { + // Instead of checking if every possible executable path + // is valid, spawn each directly and catch ENOENT + let possiblePaths = self.executable.possibleExecutablePaths( + withPathValue: self.environment.pathValue() + ) + return try self.preSpawn { args throws -> Execution in + let (env, uidPtr, gidPtr, supplementaryGroups) = args + for possibleExecutablePath in possiblePaths { + var pid: pid_t = 0 + + // Setup Arguments + let argv: [UnsafeMutablePointer?] = self.arguments.createArgs( + withExecutablePath: possibleExecutablePath + ) + defer { + for ptr in argv { ptr?.deallocate() } + } + + // Setup file actions and spawn attributes + var fileActions: posix_spawn_file_actions_t? = nil + var spawnAttributes: posix_spawnattr_t? = nil + // Setup stdin, stdout, and stderr + posix_spawn_file_actions_init(&fileActions) + defer { + posix_spawn_file_actions_destroy(&fileActions) + } + // Input + var result: Int32 = -1 + if let inputRead = inputPipe.readFileDescriptor { + result = posix_spawn_file_actions_adddup2(&fileActions, inputRead.wrapped.rawValue, 0) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + if let inputWrite = inputPipe.writeFileDescriptor { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, inputWrite.wrapped.rawValue) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + // Output + if let outputWrite = outputPipe.writeFileDescriptor { + result = posix_spawn_file_actions_adddup2(&fileActions, outputWrite.wrapped.rawValue, 1) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + if let outputRead = outputPipe.readFileDescriptor { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, outputRead.wrapped.rawValue) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + // Error + if let errorWrite = errorPipe.writeFileDescriptor { + result = posix_spawn_file_actions_adddup2(&fileActions, errorWrite.wrapped.rawValue, 2) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + if let errorRead = errorPipe.readFileDescriptor { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, errorRead.wrapped.rawValue) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + // Setup spawnAttributes + posix_spawnattr_init(&spawnAttributes) + defer { + posix_spawnattr_destroy(&spawnAttributes) + } + var noSignals = sigset_t() + var allSignals = sigset_t() + sigemptyset(&noSignals) + sigfillset(&allSignals) + posix_spawnattr_setsigmask(&spawnAttributes, &noSignals) + posix_spawnattr_setsigdefault(&spawnAttributes, &allSignals) + // Configure spawnattr + var spawnAttributeError: Int32 = 0 + var flags: Int32 = POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF + if let pgid = self.platformOptions.processGroupID { + flags |= POSIX_SPAWN_SETPGROUP + spawnAttributeError = posix_spawnattr_setpgroup(&spawnAttributes, pid_t(pgid)) + } + spawnAttributeError = posix_spawnattr_setflags(&spawnAttributes, Int16(flags)) + // Set QualityOfService + // spanattr_qos seems to only accept `QOS_CLASS_UTILITY` or `QOS_CLASS_BACKGROUND` + // and returns an error of `EINVAL` if anything else is provided + if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .utility { + spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_UTILITY) + } else if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .background { + spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_BACKGROUND) + } + + // Setup cwd + let intendedWorkingDir = self.workingDirectory.string + let chdirError: Int32 = intendedWorkingDir.withPlatformString { workDir in + return posix_spawn_file_actions_addchdir_np(&fileActions, workDir) + } + + // Error handling + if chdirError != 0 || spawnAttributeError != 0 { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + if spawnAttributeError != 0 { + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnAttributeError) + ) + } + + if chdirError != 0 { + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnAttributeError) + ) + } + } + // Run additional config + if let spawnConfig = self.platformOptions.preSpawnProcessConfigurator { + try spawnConfig(&spawnAttributes, &fileActions) + } + + // Spawn + let spawnError: CInt = possibleExecutablePath.withCString { exePath in + return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in + return _subprocess_spawn( + &pid, + exePath, + &fileActions, + &spawnAttributes, + argv, + env, + uidPtr, + gidPtr, + Int32(supplementaryGroups?.count ?? 0), + sgroups?.baseAddress, + self.platformOptions.createSession ? 1 : 0 + ) + } + } + // Spawn error + if spawnError != 0 { + if spawnError == ENOENT { + // Move on to another possible path + continue + } + // Throw all other errors + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe + ) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnError) + ) + } + return Execution( + processIdentifier: .init(value: pid), + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe + ) + } + + // If we reach this point, it means either the executable path + // or working directory is not valid. Since posix_spawn does not + // provide which one is not valid, here we make a best effort guess + // by checking whether the working directory is valid. This technically + // still causes TOUTOC issue, but it's the best we can do for error recovery. + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + let workingDirectory = self.workingDirectory.string + guard Configuration.pathAccessible(workingDirectory, mode: F_OK) else { + throw SubprocessError( + code: .init(.failedToChangeWorkingDirectory(workingDirectory)), + underlyingError: .init(rawValue: ENOENT) + ) + } + throw SubprocessError( + code: .init(.executableNotFound(self.executable.description)), + underlyingError: .init(rawValue: ENOENT) + ) + } + } +} + +// Special keys used in Error's user dictionary +extension String { + static let debugDescriptionErrorKey = "NSDebugDescription" +} + // MARK: - Process Monitoring @Sendable internal func monitorProcessTermination( - forProcessWithIdentifier pid: Subprocess.ProcessIdentifier -) async throws -> Subprocess.TerminationStatus { + forProcessWithIdentifier pid: ProcessIdentifier +) async throws -> TerminationStatus { return try await withCheckedThrowingContinuation { continuation in let source = DispatchSource.makeProcessSource( identifier: pid.value, @@ -329,7 +399,12 @@ internal func monitorProcessTermination( var siginfo = siginfo_t() let rc = waitid(P_PID, id_t(pid.value), &siginfo, WEXITED) guard rc == 0 else { - continuation.resume(throwing: POSIXError(.init(rawValue: errno) ?? .ENODEV)) + continuation.resume( + throwing: SubprocessError( + code: .init(.failedToMonitorProcess), + underlyingError: .init(rawValue: errno) + ) + ) return } switch siginfo.si_code { @@ -350,4 +425,4 @@ internal func monitorProcessTermination( } } -#endif // canImport(Darwin) +#endif // canImport(Darwin) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift index 9debf2e3..23c9b36e 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift @@ -2,196 +2,210 @@ // // 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 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// -#if canImport(Glibc) +#if canImport(Glibc) || canImport(Bionic) || canImport(Musl) + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif +#if canImport(Glibc) import Glibc -import Dispatch -import SystemPackage -import FoundationEssentials -import _CShims +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Musl) +import Musl +#endif -// Linux specific implementations -extension Subprocess.Configuration { - internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes +internal import Dispatch - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { +import Synchronization +import _SubprocessCShims + +// Linux specific implementations +extension Configuration { + internal func spawn< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { _setupMonitorSignalHandler() - let (executablePath, - env, argv, - intendedWorkingDir, - uidPtr, gidPtr, - supplementaryGroups - ) = try self.preSpawn() - var processGroupIDPtr: UnsafeMutablePointer? = nil - if let processGroupID = self.platformOptions.processGroupID { - processGroupIDPtr = .allocate(capacity: 1) - processGroupIDPtr?.pointee = gid_t(processGroupID) - } - defer { - for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - uidPtr?.deallocate() - gidPtr?.deallocate() - processGroupIDPtr?.deallocate() - } + // Instead of checking if every possible executable path + // is valid, spawn each directly and catch ENOENT + let possiblePaths = self.executable.possibleExecutablePaths( + withPathValue: self.environment.pathValue() + ) - let fileDescriptors: [CInt] = [ - input.getReadFileDescriptor()?.rawValue ?? -1, - input.getWriteFileDescriptor()?.rawValue ?? -1, - output.getWriteFileDescriptor()?.rawValue ?? -1, - output.getReadFileDescriptor()?.rawValue ?? -1, - error.getWriteFileDescriptor()?.rawValue ?? -1, - error.getReadFileDescriptor()?.rawValue ?? -1 - ] + return try self.preSpawn { args throws -> Execution in + let (env, uidPtr, gidPtr, supplementaryGroups) = args - var workingDirectory: String? - if intendedWorkingDir != FilePath.currentWorkingDirectory { - // Only pass in working directory if it's different - workingDirectory = intendedWorkingDir.string - } - // Spawn - var pid: pid_t = 0 - let spawnError: CInt = executablePath.withCString { exePath in - return workingDirectory.withOptionalCString { workingDir in - return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in - return fileDescriptors.withUnsafeBufferPointer { fds in - return _subprocess_fork_exec( - &pid, exePath, workingDir, - fds.baseAddress!, - argv, env, - uidPtr, gidPtr, - processGroupIDPtr, - CInt(supplementaryGroups?.count ?? 0), sgroups?.baseAddress, - self.platformOptions.createSession ? 1 : 0, - self.platformOptions.preSpawnProcessConfigurator - ) + for possibleExecutablePath in possiblePaths { + var processGroupIDPtr: UnsafeMutablePointer? = nil + if let processGroupID = self.platformOptions.processGroupID { + processGroupIDPtr = .allocate(capacity: 1) + processGroupIDPtr?.pointee = gid_t(processGroupID) + } + // Setup Arguments + let argv: [UnsafeMutablePointer?] = self.arguments.createArgs( + withExecutablePath: possibleExecutablePath + ) + defer { + for ptr in argv { ptr?.deallocate() } + } + // Setup input + let fileDescriptors: [CInt] = [ + inputPipe.readFileDescriptor?.wrapped.rawValue ?? -1, + inputPipe.writeFileDescriptor?.wrapped.rawValue ?? -1, + outputPipe.writeFileDescriptor?.wrapped.rawValue ?? -1, + outputPipe.readFileDescriptor?.wrapped.rawValue ?? -1, + errorPipe.writeFileDescriptor?.wrapped.rawValue ?? -1, + errorPipe.readFileDescriptor?.wrapped.rawValue ?? -1, + ] + + let workingDirectory: String = self.workingDirectory.string + // Spawn + var pid: pid_t = 0 + let spawnError: CInt = possibleExecutablePath.withCString { exePath in + return workingDirectory.withCString { workingDir in + return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in + return fileDescriptors.withUnsafeBufferPointer { fds in + return _subprocess_fork_exec( + &pid, + exePath, + workingDir, + fds.baseAddress!, + argv, + env, + uidPtr, + gidPtr, + processGroupIDPtr, + CInt(supplementaryGroups?.count ?? 0), + sgroups?.baseAddress, + self.platformOptions.createSession ? 1 : 0, + self.platformOptions.preSpawnProcessConfigurator + ) + } + } } } + // Spawn error + if spawnError != 0 { + if spawnError == ENOENT { + // Move on to another possible path + continue + } + // Throw all other errors + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe + ) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnError) + ) + } + return Execution( + processIdentifier: .init(value: pid), + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe + ) } + + // If we reach this point, it means either the executable path + // or working directory is not valid. Since posix_spawn does not + // provide which one is not valid, here we make a best effort guess + // by checking whether the working directory is valid. This technically + // still causes TOUTOC issue, but it's the best we can do for error recovery. + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + let workingDirectory = self.workingDirectory.string + guard Configuration.pathAccessible(workingDirectory, mode: F_OK) else { + throw SubprocessError( + code: .init(.failedToChangeWorkingDirectory(workingDirectory)), + underlyingError: .init(rawValue: ENOENT) + ) + } + throw SubprocessError( + code: .init(.executableNotFound(self.executable.description)), + underlyingError: .init(rawValue: ENOENT) + ) } - // Spawn error - if spawnError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) - } - return Subprocess( - processIdentifier: .init(value: pid), - executionInput: input, - executionOutput: output, - executionError: error - ) } } // MARK: - Platform Specific Options -extension Subprocess { - /// The collection of platform-specific settings - /// to configure the subprocess when running - public struct PlatformOptions: Sendable { - // Set user ID for the subprocess - public var userID: uid_t? = nil - /// Set the real and effective group ID and the saved - /// set-group-ID of the subprocess, equivalent to calling - /// `setgid()` on the child process. - /// Group ID is used to control permissions, particularly - /// for file access. - public var groupID: gid_t? = nil - // Set list of supplementary group IDs for the subprocess - public var supplementaryGroups: [gid_t]? = nil - /// Set the process group for the subprocess, equivalent to - /// calling `setpgid()` on the child process. - /// Process group ID is used to group related processes for - /// controlling signals. - public var processGroupID: pid_t? = nil - // Creates a session and sets the process group ID - // i.e. Detach from the terminal. - public var createSession: Bool = false - /// An ordered list of steps in order to tear down the child - /// process in case the parent task is cancelled before - /// the child proces terminates. - /// Always ends in sending a `.kill` signal at the end. - public var teardownSequence: [TeardownStep] = [] - /// A closure to configure platform-specific - /// spawning constructs. This closure enables direct - /// configuration or override of underlying platform-specific - /// spawn settings that `Subprocess` utilizes internally, - /// in cases where Subprocess does not provide higher-level - /// APIs for such modifications. - /// - /// On Linux, Subprocess uses `fork/exec` as the - /// underlying spawning mechanism. This closure is called - /// after `fork()` but before `exec()`. You may use it to - /// call any necessary process setup functions. - public var preSpawnProcessConfigurator: (@convention(c) @Sendable () -> Void)? = nil - public init() {} - } -} - -extension Subprocess.PlatformOptions: Hashable { - public static func ==( - lhs: Subprocess.PlatformOptions, - rhs: Subprocess.PlatformOptions - ) -> Bool { - // Since we can't compare closure equality, - // as long as preSpawnProcessConfigurator is set - // always returns false so that `PlatformOptions` - // with it set will never equal to each other - if lhs.preSpawnProcessConfigurator != nil || - rhs.preSpawnProcessConfigurator != nil { - return false - } - return lhs.userID == rhs.userID && - lhs.groupID == rhs.groupID && - lhs.supplementaryGroups == rhs.supplementaryGroups && - lhs.processGroupID == rhs.processGroupID && - lhs.createSession == rhs.createSession - } +/// The collection of platform-specific settings +/// to configure the subprocess when running +public struct PlatformOptions: Sendable { + // Set user ID for the subprocess + public var userID: uid_t? = nil + /// Set the real and effective group ID and the saved + /// set-group-ID of the subprocess, equivalent to calling + /// `setgid()` on the child process. + /// Group ID is used to control permissions, particularly + /// for file access. + public var groupID: gid_t? = nil + // Set list of supplementary group IDs for the subprocess + public var supplementaryGroups: [gid_t]? = nil + /// Set the process group for the subprocess, equivalent to + /// calling `setpgid()` on the child process. + /// Process group ID is used to group related processes for + /// controlling signals. + public var processGroupID: pid_t? = nil + // Creates a session and sets the process group ID + // i.e. Detach from the terminal. + public var createSession: Bool = false + /// An ordered list of steps in order to tear down the child + /// process in case the parent task is cancelled before + /// the child proces terminates. + /// Always ends in sending a `.kill` signal at the end. + public var teardownSequence: [TeardownStep] = [] + /// A closure to configure platform-specific + /// spawning constructs. This closure enables direct + /// configuration or override of underlying platform-specific + /// spawn settings that `Subprocess` utilizes internally, + /// in cases where Subprocess does not provide higher-level + /// APIs for such modifications. + /// + /// On Linux, Subprocess uses `fork/exec` as the + /// underlying spawning mechanism. This closure is called + /// after `fork()` but before `exec()`. You may use it to + /// call any necessary process setup functions. + public var preSpawnProcessConfigurator: (@convention(c) @Sendable () -> Void)? = nil - public func hash(into hasher: inout Hasher) { - hasher.combine(userID) - hasher.combine(groupID) - hasher.combine(supplementaryGroups) - hasher.combine(processGroupID) - hasher.combine(createSession) - // Since we can't really hash closures, - // use an UUID such that as long as - // `preSpawnProcessConfigurator` is set, it will - // never equal to other PlatformOptions - if self.preSpawnProcessConfigurator != nil { - hasher.combine(UUID()) - } - } + public init() {} } -extension Subprocess.PlatformOptions : CustomStringConvertible, CustomDebugStringConvertible { +extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible { internal func description(withIndent indent: Int) -> String { let indent = String(repeating: " ", count: indent * 4) return """ -PlatformOptions( -\(indent) userID: \(String(describing: userID)), -\(indent) groupID: \(String(describing: groupID)), -\(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), -\(indent) processGroupID: \(String(describing: processGroupID)), -\(indent) createSession: \(createSession), -\(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") -\(indent)) -""" + PlatformOptions( + \(indent) userID: \(String(describing: userID)), + \(indent) groupID: \(String(describing: groupID)), + \(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), + \(indent) processGroupID: \(String(describing: processGroupID)), + \(indent) createSession: \(createSession), + \(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") + \(indent)) + """ } public var description: String { @@ -211,12 +225,13 @@ extension String { // MARK: - Process Monitoring @Sendable internal func monitorProcessTermination( - forProcessWithIdentifier pid: Subprocess.ProcessIdentifier -) async throws -> Subprocess.TerminationStatus { + forProcessWithIdentifier pid: ProcessIdentifier +) async throws -> TerminationStatus { return try await withCheckedThrowingContinuation { continuation in _childProcessContinuations.withLock { continuations in if let existing = continuations.removeValue(forKey: pid.value), - case .status(let existingStatus) = existing { + case .status(let existingStatus) = existing + { // We already have existing status to report continuation.resume(returning: existingStatus) } else { @@ -228,28 +243,26 @@ internal func monitorProcessTermination( } private enum ContinuationOrStatus { - case continuation(CheckedContinuation) - case status(Subprocess.TerminationStatus) + case continuation(CheckedContinuation) + case status(TerminationStatus) } -private let _childProcessContinuations: LockedState< - [pid_t: ContinuationOrStatus] -> = LockedState(initialState: [:]) +private let _childProcessContinuations: + Mutex< + [pid_t: ContinuationOrStatus] + > = Mutex([:]) + +private let signalSource: SendableSourceSignal = SendableSourceSignal() -private var signalSource: (any DispatchSourceSignal)? = nil private let setup: () = { - signalSource = DispatchSource.makeSignalSource( - signal: SIGCHLD, - queue: .global() - ) - signalSource?.setEventHandler { + signalSource.setEventHandler { _childProcessContinuations.withLock { continuations in while true { var siginfo = siginfo_t() guard waitid(P_ALL, id_t(0), &siginfo, WEXITED) == 0 else { return } - var status: Subprocess.TerminationStatus? = nil + var status: TerminationStatus? = nil switch siginfo.si_code { case .init(CLD_EXITED): status = .exited(siginfo._sifields._sigchld.si_status) @@ -265,7 +278,8 @@ private let setup: () = { if let status = status { let pid = siginfo._sifields._sigchld.si_pid if let existing = continuations.removeValue(forKey: pid), - case .continuation(let c) = existing { + case .continuation(let c) = existing + { c.resume(returning: status) } else { // We don't have continuation yet, just state status @@ -275,13 +289,33 @@ private let setup: () = { } } } - signalSource?.resume() + signalSource.resume() }() +/// Unchecked Sendable here since this class is only explicitly +/// initialzied once during the lifetime of the process +final class SendableSourceSignal: @unchecked Sendable { + private let signalSource: DispatchSourceSignal + + func setEventHandler(handler: @escaping DispatchSourceHandler) { + self.signalSource.setEventHandler(handler: handler) + } + + func resume() { + self.signalSource.resume() + } + + init() { + self.signalSource = DispatchSource.makeSignalSource( + signal: SIGCHLD, + queue: .global() + ) + } +} + private func _setupMonitorSignalHandler() { // Only executed once setup } -#endif // canImport(Glibc) - +#endif // canImport(Glibc) || canImport(Bionic) || canImport(Musl) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift index bd110301..ae8fd639 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift @@ -2,119 +2,143 @@ // // 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 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// -#if canImport(Darwin) || canImport(Glibc) +#if canImport(Darwin) || canImport(Glibc) || canImport(Bionic) || canImport(Musl) -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage #endif +import _SubprocessCShims + #if canImport(Darwin) import Darwin +#elseif canImport(Bionic) +import Bionic #elseif canImport(Glibc) import Glibc +#elseif canImport(Musl) +import Musl #endif -#if FOUNDATION_FRAMEWORK -@_implementationOnly import _FoundationCShims -#else -import _CShims -#endif - -import Dispatch -import SystemPackage +package import Dispatch // MARK: - Signals -extension Subprocess { - /// Signals are standardized messages sent to a running program - /// to trigger specific behavior, such as quitting or error handling. - public struct Signal : Hashable, Sendable { - /// The underlying platform specific value for the signal - public let rawValue: Int32 - - private init(rawValue: Int32) { - self.rawValue = rawValue - } - /// The `.interrupt` signal is sent to a process by its - /// controlling terminal when a user wishes to interrupt - /// the process. - public static var interrupt: Self { .init(rawValue: SIGINT) } - /// The `.terminate` signal is sent to a process to request its - /// termination. Unlike the `.kill` signal, it can be caught - /// and interpreted or ignored by the process. This allows - /// the process to perform nice termination releasing resources - /// and saving state if appropriate. `.interrupt` is nearly - /// identical to `.terminate`. - public static var terminate: Self { .init(rawValue: SIGTERM) } - /// The `.suspend` signal instructs the operating system - /// to stop a process for later resumption. - public static var suspend: Self { .init(rawValue: SIGSTOP) } - /// The `resume` signal instructs the operating system to - /// continue (restart) a process previously paused by the - /// `.suspend` signal. - public static var resume: Self { .init(rawValue: SIGCONT) } - /// The `.kill` signal is sent to a process to cause it to - /// terminate immediately (kill). In contrast to `.terminate` - /// and `.interrupt`, this signal cannot be caught or ignored, - /// and the receiving process cannot perform any - /// clean-up upon receiving this signal. - public static var kill: Self { .init(rawValue: SIGKILL) } - /// The `.terminalClosed` signal is sent to a process when - /// its controlling terminal is closed. In modern systems, - /// this signal usually means that the controlling pseudo - /// or virtual terminal has been closed. - public static var terminalClosed: Self { .init(rawValue: SIGHUP) } - /// The `.quit` signal is sent to a process by its controlling - /// terminal when the user requests that the process quit - /// and perform a core dump. - public static var quit: Self { .init(rawValue: SIGQUIT) } - /// The `.userDefinedOne` signal is sent to a process to indicate - /// user-defined conditions. - public static var userDefinedOne: Self { .init(rawValue: SIGUSR1) } - /// The `.userDefinedTwo` signal is sent to a process to indicate - /// user-defined conditions. - public static var userDefinedTwo: Self { .init(rawValue: SIGUSR2) } - /// The `.alarm` signal is sent to a process when the corresponding - /// time limit is reached. - public static var alarm: Self { .init(rawValue: SIGALRM) } - /// The `.windowSizeChange` signal is sent to a process when - /// its controlling terminal changes its size (a window change). - public static var windowSizeChange: Self { .init(rawValue: SIGWINCH) } +/// Signals are standardized messages sent to a running program +/// to trigger specific behavior, such as quitting or error handling. +public struct Signal: Hashable, Sendable { + /// The underlying platform specific value for the signal + public let rawValue: Int32 + + private init(rawValue: Int32) { + self.rawValue = rawValue + } + + /// The `.interrupt` signal is sent to a process by its + /// controlling terminal when a user wishes to interrupt + /// the process. + public static var interrupt: Self { .init(rawValue: SIGINT) } + /// The `.terminate` signal is sent to a process to request its + /// termination. Unlike the `.kill` signal, it can be caught + /// and interpreted or ignored by the process. This allows + /// the process to perform nice termination releasing resources + /// and saving state if appropriate. `.interrupt` is nearly + /// identical to `.terminate`. + public static var terminate: Self { .init(rawValue: SIGTERM) } + /// The `.suspend` signal instructs the operating system + /// to stop a process for later resumption. + public static var suspend: Self { .init(rawValue: SIGSTOP) } + /// The `resume` signal instructs the operating system to + /// continue (restart) a process previously paused by the + /// `.suspend` signal. + public static var resume: Self { .init(rawValue: SIGCONT) } + /// The `.kill` signal is sent to a process to cause it to + /// terminate immediately (kill). In contrast to `.terminate` + /// and `.interrupt`, this signal cannot be caught or ignored, + /// and the receiving process cannot perform any + /// clean-up upon receiving this signal. + public static var kill: Self { .init(rawValue: SIGKILL) } + /// The `.terminalClosed` signal is sent to a process when + /// its controlling terminal is closed. In modern systems, + /// this signal usually means that the controlling pseudo + /// or virtual terminal has been closed. + public static var terminalClosed: Self { .init(rawValue: SIGHUP) } + /// The `.quit` signal is sent to a process by its controlling + /// terminal when the user requests that the process quit + /// and perform a core dump. + public static var quit: Self { .init(rawValue: SIGQUIT) } + /// The `.userDefinedOne` signal is sent to a process to indicate + /// user-defined conditions. + public static var userDefinedOne: Self { .init(rawValue: SIGUSR1) } + /// The `.userDefinedTwo` signal is sent to a process to indicate + /// user-defined conditions. + public static var userDefinedTwo: Self { .init(rawValue: SIGUSR2) } + /// The `.alarm` signal is sent to a process when the corresponding + /// time limit is reached. + public static var alarm: Self { .init(rawValue: SIGALRM) } + /// The `.windowSizeChange` signal is sent to a process when + /// its controlling terminal changes its size (a window change). + public static var windowSizeChange: Self { .init(rawValue: SIGWINCH) } +} + +// MARK: - ProcessIdentifier + +/// A platform independent identifier for a Subprocess. +public struct ProcessIdentifier: Sendable, Hashable, Codable { + /// The platform specific process identifier value + public let value: pid_t + + public init(value: pid_t) { + self.value = value } +} +extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { "\(self.value)" } + + public var debugDescription: String { "\(self.value)" } +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { /// Send the given signal to the child process. /// - Parameters: /// - signal: The signal to send. /// - shouldSendToProcessGroup: Whether this signal should be sent to /// the entire process group. - public func send(_ signal: Signal, toProcessGroup shouldSendToProcessGroup: Bool) throws { + public func send( + signal: Signal, + toProcessGroup shouldSendToProcessGroup: Bool = false + ) throws { let pid = shouldSendToProcessGroup ? -(self.processIdentifier.value) : self.processIdentifier.value guard kill(pid, signal.rawValue) == 0 else { - throw POSIXError(.init(rawValue: errno)!) + throw SubprocessError( + code: .init(.failedToSendSignal(signal.rawValue)), + underlyingError: .init(rawValue: errno) + ) } } - internal func tryTerminate() -> Error? { + internal func tryTerminate() -> Swift.Error? { do { - try self.send(.kill, toProcessGroup: true) + try self.send(signal: .kill) } catch { - guard let posixError: POSIXError = error as? POSIXError else { + guard let posixError: SubprocessError = error as? SubprocessError else { return error } // Ignore ESRCH (no such process) - if posixError.code != .ESRCH { + if let underlyingError = posixError.underlyingError, + underlyingError.rawValue != ESRCH + { return error } } @@ -123,21 +147,32 @@ extension Subprocess { } // MARK: - Environment Resolution -extension Subprocess.Environment { - internal static let pathEnvironmentVariableName = "PATH" +extension Environment { + internal static let pathVariableName = "PATH" internal func pathValue() -> String? { switch self.config { case .inherit(let overrides): // If PATH value exists in overrides, use it - if let value = overrides[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = overrides[Self.pathVariableName] { + return value } // Fall back to current process - return ProcessInfo.processInfo.environment[Self.pathEnvironmentVariableName] + return Self.currentEnvironmentValues()[Self.pathVariableName] case .custom(let fullEnvironment): - if let value = fullEnvironment[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = fullEnvironment[Self.pathVariableName] { + return value + } + return nil + case .rawBytes(let rawBytesArray): + let needle: [UInt8] = Array("\(Self.pathVariableName)=".utf8) + for row in rawBytesArray { + guard row.starts(with: needle) else { + continue + } + // Attempt to + let pathValue = row.dropFirst(needle.count) + return String(decoding: pathValue, as: UTF8.self) } return nil } @@ -147,8 +182,8 @@ extension Subprocess.Environment { // manually deallocated internal func createEnv() -> [UnsafeMutablePointer?] { func createFullCString( - fromKey keyContainer: Subprocess.StringOrRawBytes, - value valueContainer: Subprocess.StringOrRawBytes + fromKey keyContainer: StringOrRawBytes, + value valueContainer: StringOrRawBytes ) -> UnsafeMutablePointer { let rawByteKey: UnsafeMutablePointer = keyContainer.createRawBytes() let rawByteValue: UnsafeMutablePointer = valueContainer.createRawBytes() @@ -170,21 +205,12 @@ extension Subprocess.Environment { var env: [UnsafeMutablePointer?] = [] switch self.config { case .inherit(let updates): - var current = ProcessInfo.processInfo.environment - for (keyContainer, valueContainer) in updates { - if let stringKey = keyContainer.stringValue { - // Remove the value from current to override it - current.removeValue(forKey: stringKey) - } - // Fast path - if case .string(let stringKey) = keyContainer, - case .string(let stringValue) = valueContainer { - let fullString = "\(stringKey)=\(stringValue)" - env.append(strdup(fullString)) - continue - } - - env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) + var current = Self.currentEnvironmentValues() + for (key, value) in updates { + // Remove the value from current to override it + current.removeValue(forKey: key) + let fullString = "\(key)=\(value)" + env.append(strdup(fullString)) } // Add the rest of `current` to env for (key, value) in current { @@ -192,24 +218,43 @@ extension Subprocess.Environment { env.append(strdup(fullString)) } case .custom(let customValues): - for (keyContainer, valueContainer) in customValues { - // Fast path - if case .string(let stringKey) = keyContainer, - case .string(let stringValue) = valueContainer { - let fullString = "\(stringKey)=\(stringValue)" - env.append(strdup(fullString)) - continue - } - env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) + for (key, value) in customValues { + let fullString = "\(key)=\(value)" + env.append(strdup(fullString)) + } + case .rawBytes(let rawBytesArray): + for rawBytes in rawBytesArray { + env.append(strdup(rawBytes)) } } env.append(nil) return env } + + internal static func withCopiedEnv(_ body: ([UnsafeMutablePointer]) -> R) -> R { + var values: [UnsafeMutablePointer] = [] + // This lock is taken by calls to getenv, so we want as few callouts to other code as possible here. + _subprocess_lock_environ() + guard + let environments: UnsafeMutablePointer?> = + _subprocess_get_environ() + else { + _subprocess_unlock_environ() + return body([]) + } + var curr = environments + while let value = curr.pointee { + values.append(strdup(value)) + curr = curr.advanced(by: 1) + } + _subprocess_unlock_environ() + defer { values.forEach { free($0) } } + return body(values) + } } // MARK: Args Creation -extension Subprocess.Arguments { +extension Arguments { // This method follows the standard "create" rule: `args` needs to be // manually deallocated internal func createArgs(withExecutablePath executablePath: String) -> [UnsafeMutablePointer?] { @@ -225,42 +270,23 @@ extension Subprocess.Arguments { } } -// MARK: - ProcessIdentifier -extension Subprocess { - /// A platform independent identifier for a subprocess. - public struct ProcessIdentifier: Sendable, Hashable, Codable { - /// The platform specific process identifier value - public let value: pid_t - - public init(value: pid_t) { - self.value = value - } - } -} - -extension Subprocess.ProcessIdentifier : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { "\(self.value)" } - - public var debugDescription: String { "\(self.value)" } -} - // MARK: - Executable Searching -extension Subprocess.Executable { +extension Executable { internal static var defaultSearchPaths: Set { return Set([ "/usr/bin", "/bin", "/usr/sbin", "/sbin", - "/usr/local/bin" + "/usr/local/bin", ]) } - internal func resolveExecutablePath(withPathValue pathValue: String?) -> String? { + internal func resolveExecutablePath(withPathValue pathValue: String?) throws -> String { switch self.storage { case .executable(let executableName): // If the executableName in is already a full path, return it directly - if Subprocess.Configuration.pathAccessible(executableName, mode: X_OK) { + if Configuration.pathAccessible(executableName, mode: X_OK) { return executableName } // Get $PATH from environment @@ -274,50 +300,38 @@ extension Subprocess.Executable { for path in searchPaths { let fullPath = "\(path)/\(executableName)" - let fileExists = Subprocess.Configuration.pathAccessible(fullPath, mode: X_OK) + let fileExists = Configuration.pathAccessible(fullPath, mode: X_OK) if fileExists { return fullPath } } + throw SubprocessError( + code: .init(.executableNotFound(executableName)), + underlyingError: nil + ) case .path(let executablePath): // Use path directly return executablePath.string } - return nil } } -// MARK: - Configuration -extension Subprocess.Configuration { - internal func preSpawn() throws -> ( - executablePath: String, +// MARK: - PreSpawn +extension Configuration { + internal typealias PreSpawnArgs = ( env: [UnsafeMutablePointer?], - argv: [UnsafeMutablePointer?], - intendedWorkingDir: FilePath, uidPtr: UnsafeMutablePointer?, gidPtr: UnsafeMutablePointer?, supplementaryGroups: [gid_t]? - ) { + ) + + internal func preSpawn( + _ work: (PreSpawnArgs) throws -> Result + ) throws -> Result { // Prepare environment let env = self.environment.createEnv() - // Prepare executable path - guard let executablePath = self.executable.resolveExecutablePath( - withPathValue: self.environment.pathValue()) else { - for ptr in env { ptr?.deallocate() } - throw CocoaError(.executableNotLoadable, userInfo: [ - .debugDescriptionErrorKey : "\(self.executable.description) is not an executable" - ]) - } - // Prepare arguments - let argv: [UnsafeMutablePointer?] = self.arguments.createArgs(withExecutablePath: executablePath) - // Prepare workingDir - let intendedWorkingDir = self.workingDirectory - guard Self.pathAccessible(intendedWorkingDir.string, mode: F_OK) else { + defer { for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - throw CocoaError(.fileNoSuchFile, userInfo: [ - .debugDescriptionErrorKey : "Failed to set working directory to \(intendedWorkingDir)" - ]) } var uidPtr: UnsafeMutablePointer? = nil @@ -325,21 +339,28 @@ extension Subprocess.Configuration { uidPtr = .allocate(capacity: 1) uidPtr?.pointee = userID } + defer { + uidPtr?.deallocate() + } var gidPtr: UnsafeMutablePointer? = nil if let groupID = self.platformOptions.groupID { gidPtr = .allocate(capacity: 1) gidPtr?.pointee = groupID } + defer { + gidPtr?.deallocate() + } var supplementaryGroups: [gid_t]? if let groupsValue = self.platformOptions.supplementaryGroups { supplementaryGroups = groupsValue } - return ( - executablePath: executablePath, - env: env, argv: argv, - intendedWorkingDir: intendedWorkingDir, - uidPtr: uidPtr, gidPtr: gidPtr, - supplementaryGroups: supplementaryGroups + return try work( + ( + env: env, + uidPtr: uidPtr, + gidPtr: gidPtr, + supplementaryGroups: supplementaryGroups + ) ) } @@ -359,11 +380,11 @@ extension FileDescriptor { return devnull } - internal var platformDescriptor: Subprocess.PlatformFileDescriptor { + internal var platformDescriptor: PlatformFileDescriptor { return self } - internal func readChunk(upToLength maxLength: Int) async throws -> Data? { + package func readChunk(upToLength maxLength: Int) async throws -> SequenceOutput.Buffer? { return try await withCheckedThrowingContinuation { continuation in DispatchIO.read( fromFileDescriptor: self.rawValue, @@ -371,88 +392,125 @@ extension FileDescriptor { runningHandlerOn: .global() ) { data, error in if error != 0 { - continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV)) + continuation.resume( + throwing: SubprocessError( + code: .init(.failedToReadFromSubprocess), + underlyingError: .init(rawValue: error) + ) + ) return } if data.isEmpty { continuation.resume(returning: nil) } else { - continuation.resume(returning: Data(data)) + continuation.resume(returning: SequenceOutput.Buffer(data: data)) } } } } - internal func readUntilEOF(upToLength maxLength: Int) async throws -> Data { - return try await withCheckedThrowingContinuation { continuation in - let dispatchIO = DispatchIO( - type: .stream, - fileDescriptor: self.rawValue, - queue: .global() - ) { error in - if error != 0 { - continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV)) - } + internal func readUntilEOF( + upToLength maxLength: Int, + resultHandler: sending @escaping (Swift.Result) -> Void + ) { + let dispatchIO = DispatchIO( + type: .stream, + fileDescriptor: self.rawValue, + queue: .global() + ) { error in } + var buffer: DispatchData? + dispatchIO.read( + offset: 0, + length: maxLength, + queue: .global() + ) { done, data, error in + guard error == 0, let chunkData = data else { + dispatchIO.close() + resultHandler( + .failure( + SubprocessError( + code: .init(.failedToReadFromSubprocess), + underlyingError: .init(rawValue: error) + ) + ) + ) + return } - var buffer: Data = Data() - dispatchIO.read( - offset: 0, - length: maxLength, - queue: .global() - ) { done, data, error in - guard error == 0 else { - continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV)) - return - } - if let data = data { - buffer += Data(data) - } - if done { - dispatchIO.close() - continuation.resume(returning: buffer) - } + // Easy case: if we are done and buffer is nil, this means + // there is only one chunk of data + if done && buffer == nil { + dispatchIO.close() + buffer = chunkData + resultHandler(.success(chunkData)) + return + } + + if buffer == nil { + buffer = chunkData + } else { + buffer?.append(chunkData) + } + + if done { + dispatchIO.close() + resultHandler(.success(buffer!)) + return } } } - internal func write(_ data: S) async throws where S.Element == UInt8 { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in - let dispatchData: DispatchData = Array(data).withUnsafeBytes { - return DispatchData(bytes: $0) - } - DispatchIO.write( - toFileDescriptor: self.rawValue, - data: dispatchData, - runningHandlerOn: .global() - ) { _, error in - guard error == 0 else { - continuation.resume( - throwing: POSIXError( - .init(rawValue: error) ?? .ENODEV) + package func write( + _ array: [UInt8] + ) async throws -> Int { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + let dispatchData = array.withUnsafeBytes { + return DispatchData( + bytesNoCopy: $0, + deallocator: .custom( + nil, + { + // noop + } ) - return + ) + } + self.write(dispatchData) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) } - continuation.resume() } } } -} -extension Subprocess { - internal typealias PlatformFileDescriptor = FileDescriptor -} - -// MARK: - Read Buffer Size -extension Subprocess { - @inline(__always) - internal static var readBufferSize: Int { -#if canImport(Darwin) - return 16384 -#else - // FIXME: Use Platform.pageSize here - return 4096 -#endif // canImport(Darwin) + package func write( + _ dispatchData: DispatchData, + queue: DispatchQueue = .global(), + completion: @escaping (Int, Error?) -> Void + ) { + DispatchIO.write( + toFileDescriptor: self.rawValue, + data: dispatchData, + runningHandlerOn: queue + ) { unwritten, error in + let unwrittenLength = unwritten?.count ?? 0 + let writtenLength = dispatchData.count - unwrittenLength + guard error != 0 else { + completion(writtenLength, nil) + return + } + completion( + writtenLength, + SubprocessError( + code: .init(.failedToWriteToSubprocess), + underlyingError: .init(rawValue: error) + ) + ) + } } } -#endif // canImport(Darwin) || canImport(Glibc) +internal typealias PlatformFileDescriptor = FileDescriptor + +#endif // canImport(Darwin) || canImport(Glibc) || canImport(Bionic) || canImport(Musl) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift index 978cb139..aaf53ea0 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift @@ -2,53 +2,66 @@ // // 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 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// #if canImport(WinSDK) import WinSDK -import Dispatch -import SystemPackage -import FoundationEssentials +internal import Dispatch +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif // Windows specific implementation -extension Subprocess.Configuration { - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { +extension Configuration { + internal func spawn< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { // Spawn differently depending on whether // we need to spawn as a user - if let userCredentials = self.platformOptions.userCredentials { - return try self.spawnAsUser( - withInput: input, - output: output, - error: error, - userCredentials: userCredentials - ) - } else { + guard let userCredentials = self.platformOptions.userCredentials else { return try self.spawnDirect( - withInput: input, + withInput: inputPipe, output: output, - error: error + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe ) } + return try self.spawnAsUser( + withInput: inputPipe, + output: output, + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe, + userCredentials: userCredentials + ) } - internal func spawnDirect( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { + internal func spawnDirect< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { let ( applicationName, commandAndArgs, @@ -56,9 +69,9 @@ extension Subprocess.Configuration { intendedWorkingDir ) = try self.preSpawn() var startupInfo = try self.generateStartupInfo( - withInput: input, - output: output, - error: error + withInput: inputPipe, + output: outputPipe, + error: errorPipe ) var processInfo: PROCESS_INFORMATION = PROCESS_INFORMATION() var createProcessFlags = self.generateCreateProcessFlag() @@ -78,8 +91,8 @@ extension Subprocess.Configuration { let created = CreateProcessW( applicationNameW, UnsafeMutablePointer(mutating: commandAndArgsW), - nil, // lpProcessAttributes - nil, // lpThreadAttributes + nil, // lpProcessAttributes + nil, // lpThreadAttributes true, // bInheritHandles createProcessFlags, UnsafeMutableRawPointer(mutating: environmentW), @@ -89,14 +102,14 @@ extension Subprocess.Configuration { ) guard created else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } } @@ -106,47 +119,52 @@ extension Subprocess.Configuration { // We don't need the handle objects, so close it right away guard CloseHandle(processInfo.hThread) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } guard CloseHandle(processInfo.hProcess) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } - let pid = Subprocess.ProcessIdentifier( - processID: processInfo.dwProcessId, - threadID: processInfo.dwThreadId + let pid = ProcessIdentifier( + value: processInfo.dwProcessId ) - return Subprocess( + return Execution( processIdentifier: pid, - executionInput: input, - executionOutput: output, - executionError: error, + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe, consoleBehavior: self.platformOptions.consoleBehavior ) } - internal func spawnAsUser( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput, - userCredentials: Subprocess.PlatformOptions.UserCredentials - ) throws -> Subprocess { + internal func spawnAsUser< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe, + userCredentials: PlatformOptions.UserCredentials + ) throws -> Execution { let ( applicationName, commandAndArgs, @@ -154,9 +172,9 @@ extension Subprocess.Configuration { intendedWorkingDir ) = try self.preSpawn() var startupInfo = try self.generateStartupInfo( - withInput: input, - output: output, - error: error + withInput: inputPipe, + output: outputPipe, + error: errorPipe ) var processInfo: PROCESS_INFORMATION = PROCESS_INFORMATION() var createProcessFlags = self.generateCreateProcessFlag() @@ -197,14 +215,14 @@ extension Subprocess.Configuration { ) guard created else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } } @@ -217,201 +235,175 @@ extension Subprocess.Configuration { // We don't need the handle objects, so close it right away guard CloseHandle(processInfo.hThread) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } guard CloseHandle(processInfo.hProcess) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } - let pid = Subprocess.ProcessIdentifier( - processID: processInfo.dwProcessId, - threadID: processInfo.dwThreadId + let pid = ProcessIdentifier( + value: processInfo.dwProcessId ) - return Subprocess( + return Execution( processIdentifier: pid, - executionInput: input, - executionOutput: output, - executionError: error, + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe, consoleBehavior: self.platformOptions.consoleBehavior ) } } // MARK: - Platform Specific Options -extension Subprocess { - /// The collection of platform-specific settings - /// to configure the subprocess when running - public struct PlatformOptions: Sendable { - /// A `UserCredentials` to use spawning the subprocess - /// as a different user - public struct UserCredentials: Sendable, Hashable { - // The name of the user. This is the name - // of the user account to run as. - public var username: String - // The clear-text password for the account. - public var password: String - // The name of the domain or server whose account database - // contains the account. - public var domain: String? - } - /// `ConsoleBehavior` defines how should the console appear - /// when spawning a new process - public struct ConsoleBehavior: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case createNew - case detatch - case inherit - } +/// The collection of platform-specific settings +/// to configure the subprocess when running +public struct PlatformOptions: Sendable { + /// A `UserCredentials` to use spawning the subprocess + /// as a different user + public struct UserCredentials: Sendable, Hashable { + // The name of the user. This is the name + // of the user account to run as. + public var username: String + // The clear-text password for the account. + public var password: String + // The name of the domain or server whose account database + // contains the account. + public var domain: String? + } - internal let storage: Storage + /// `ConsoleBehavior` defines how should the console appear + /// when spawning a new process + public struct ConsoleBehavior: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case createNew + case detatch + case inherit + } - private init(_ storage: Storage) { - self.storage = storage - } + internal let storage: Storage - /// The subprocess has a new console, instead of - /// inheriting its parent's console (the default). - public static let createNew: Self = .init(.createNew) - /// For console processes, the new process does not - /// inherit its parent's console (the default). - /// The new process can call the `AllocConsole` - /// function at a later time to create a console. - public static let detatch: Self = .init(.detatch) - /// The subprocess inherits its parent's console. - public static let inherit: Self = .init(.inherit) + private init(_ storage: Storage) { + self.storage = storage } - /// `ConsoleBehavior` defines how should the window appear - /// when spawning a new process - public struct WindowStyle: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case normal - case hidden - case maximized - case minimized - } + /// The subprocess has a new console, instead of + /// inheriting its parent's console (the default). + public static let createNew: Self = .init(.createNew) + /// For console processes, the new process does not + /// inherit its parent's console (the default). + /// The new process can call the `AllocConsole` + /// function at a later time to create a console. + public static let detatch: Self = .init(.detatch) + /// The subprocess inherits its parent's console. + public static let inherit: Self = .init(.inherit) + } - internal let storage: Storage + /// `ConsoleBehavior` defines how should the window appear + /// when spawning a new process + public struct WindowStyle: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case normal + case hidden + case maximized + case minimized + } - internal var platformStyle: WORD { - switch self.storage { - case .hidden: return WORD(SW_HIDE) - case .maximized: return WORD(SW_SHOWMAXIMIZED) - case .minimized: return WORD(SW_SHOWMINIMIZED) - default: return WORD(SW_SHOWNORMAL) - } - } + internal let storage: Storage - private init(_ storage: Storage) { - self.storage = storage + internal var platformStyle: WORD { + switch self.storage { + case .hidden: return WORD(SW_HIDE) + case .maximized: return WORD(SW_SHOWMAXIMIZED) + case .minimized: return WORD(SW_SHOWMINIMIZED) + default: return WORD(SW_SHOWNORMAL) } + } - /// Activates and displays a window of normal size - public static let normal: Self = .init(.normal) - /// Does not activate a new window - public static let hidden: Self = .init(.hidden) - /// Activates the window and displays it as a maximized window. - public static let maximized: Self = .init(.maximized) - /// Activates the window and displays it as a minimized window. - public static let minimized: Self = .init(.minimized) + private init(_ storage: Storage) { + self.storage = storage } - /// Sets user credentials when starting the process as another user - public var userCredentials: UserCredentials? = nil - /// The console behavior of the new process, - /// default to inheriting the console from parent process - public var consoleBehavior: ConsoleBehavior = .inherit - /// Window style to use when the process is started - public var windowStyle: WindowStyle = .normal - /// Whether to create a new process group for the new - /// process. The process group includes all processes - /// that are descendants of this root process. - /// The process identifier of the new process group - /// is the same as the process identifier. - public var createProcessGroup: Bool = false - /// A closure to configure platform-specific - /// spawning constructs. This closure enables direct - /// configuration or override of underlying platform-specific - /// spawn settings that `Subprocess` utilizes internally, - /// in cases where Subprocess does not provide higher-level - /// APIs for such modifications. - /// - /// On Windows, Subprocess uses `CreateProcessW()` as the - /// underlying spawning mechanism. This closure allows - /// modification of the `dwCreationFlags` creation flag - /// and startup info `STARTUPINFOW` before - /// they are sent to `CreateProcessW()`. - public var preSpawnProcessConfigurator: ( + /// Activates and displays a window of normal size + public static let normal: Self = .init(.normal) + /// Does not activate a new window + public static let hidden: Self = .init(.hidden) + /// Activates the window and displays it as a maximized window. + public static let maximized: Self = .init(.maximized) + /// Activates the window and displays it as a minimized window. + public static let minimized: Self = .init(.minimized) + } + + /// Sets user credentials when starting the process as another user + public var userCredentials: UserCredentials? = nil + /// The console behavior of the new process, + /// default to inheriting the console from parent process + public var consoleBehavior: ConsoleBehavior = .inherit + /// Window style to use when the process is started + public var windowStyle: WindowStyle = .normal + /// Whether to create a new process group for the new + /// process. The process group includes all processes + /// that are descendants of this root process. + /// The process identifier of the new process group + /// is the same as the process identifier. + public var createProcessGroup: Bool = false + /// An ordered list of steps in order to tear down the child + /// process in case the parent task is cancelled before + /// the child proces terminates. + /// Always ends in forcefully terminate at the end. + public var teardownSequence: [TeardownStep] = [] + /// A closure to configure platform-specific + /// spawning constructs. This closure enables direct + /// configuration or override of underlying platform-specific + /// spawn settings that `Subprocess` utilizes internally, + /// in cases where Subprocess does not provide higher-level + /// APIs for such modifications. + /// + /// On Windows, Subprocess uses `CreateProcessW()` as the + /// underlying spawning mechanism. This closure allows + /// modification of the `dwCreationFlags` creation flag + /// and startup info `STARTUPINFOW` before + /// they are sent to `CreateProcessW()`. + public var preSpawnProcessConfigurator: + ( @Sendable ( inout DWORD, inout STARTUPINFOW ) throws -> Void )? = nil - public init() {} - } -} - -extension Subprocess.PlatformOptions: Hashable { - public static func == ( - lhs: Subprocess.PlatformOptions, - rhs: Subprocess.PlatformOptions - ) -> Bool { - // Since we can't compare closure equality, - // as long as preSpawnProcessConfigurator is set - // always returns false so that `PlatformOptions` - // with it set will never equal to each other - if lhs.preSpawnProcessConfigurator != nil || rhs.preSpawnProcessConfigurator != nil { - return false - } - return lhs.userCredentials == rhs.userCredentials && lhs.consoleBehavior == rhs.consoleBehavior && lhs.windowStyle == rhs.windowStyle && - lhs.createProcessGroup == rhs.createProcessGroup - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(userCredentials) - hasher.combine(consoleBehavior) - hasher.combine(windowStyle) - hasher.combine(createProcessGroup) - // Since we can't really hash closures, - // use an UUID such that as long as - // `preSpawnProcessConfigurator` is set, it will - // never equal to other PlatformOptions - if self.preSpawnProcessConfigurator != nil { - hasher.combine(UUID()) - } - } + public init() {} } -extension Subprocess.PlatformOptions : CustomStringConvertible, CustomDebugStringConvertible { +extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible { internal func description(withIndent indent: Int) -> String { let indent = String(repeating: " ", count: indent * 4) return """ -PlatformOptions( -\(indent) userCredentials: \(String(describing: self.userCredentials)), -\(indent) consoleBehavior: \(String(describing: self.consoleBehavior)), -\(indent) windowStyle: \(String(describing: self.windowStyle)), -\(indent) createProcessGroup: \(self.createProcessGroup), -\(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") -\(indent)) -""" + PlatformOptions( + \(indent) userCredentials: \(String(describing: self.userCredentials)), + \(indent) consoleBehavior: \(String(describing: self.consoleBehavior)), + \(indent) windowStyle: \(String(describing: self.windowStyle)), + \(indent) createProcessGroup: \(self.createProcessGroup), + \(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") + \(indent)) + """ } public var description: String { @@ -426,8 +418,8 @@ PlatformOptions( // MARK: - Process Monitoring @Sendable internal func monitorProcessTermination( - forProcessWithIdentifier pid: Subprocess.ProcessIdentifier -) async throws -> Subprocess.TerminationStatus { + forProcessWithIdentifier pid: ProcessIdentifier +) async throws -> TerminationStatus { // Once the continuation resumes, it will need to unregister the wait, so // yield the wait handle back to the calling scope. var waitHandle: HANDLE? @@ -436,11 +428,13 @@ internal func monitorProcessTermination( _ = UnregisterWait(waitHandle) } } - guard let processHandle = OpenProcess( - DWORD(PROCESS_QUERY_INFORMATION | SYNCHRONIZE), - false, - pid.processID - ) else { + guard + let processHandle = OpenProcess( + DWORD(PROCESS_QUERY_INFORMATION | SYNCHRONIZE), + false, + pid.value + ) + else { return .exited(1) } @@ -449,19 +443,29 @@ internal func monitorProcessTermination( // other work. let context = Unmanaged.passRetained(continuation as AnyObject).toOpaque() let callback: WAITORTIMERCALLBACK = { context, _ in - let continuation = Unmanaged.fromOpaque(context!).takeRetainedValue() as! CheckedContinuation + let continuation = + Unmanaged.fromOpaque(context!).takeRetainedValue() as! CheckedContinuation continuation.resume() } // We only want the callback to fire once (and not be rescheduled.) Waiting // may take an arbitrarily long time, so let the thread pool know that too. let flags = ULONG(WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION) - guard RegisterWaitForSingleObject( - &waitHandle, processHandle, callback, context, INFINITE, flags - ) else { - continuation.resume(throwing: CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown) + guard + RegisterWaitForSingleObject( + &waitHandle, + processHandle, + callback, + context, + INFINITE, + flags + ) + else { + continuation.resume( + throwing: SubprocessError( + code: .init(.failedToMonitorProcess), + underlyingError: .init(rawValue: GetLastError()) + ) ) return } @@ -474,58 +478,62 @@ internal func monitorProcessTermination( return .exited(1) } let exitCodeValue = CInt(bitPattern: .init(status)) - if exitCodeValue >= 0 { - return .exited(status) - } else { + guard exitCodeValue >= 0 else { return .unhandledException(status) } + return .exited(status) } // MARK: - Subprocess Control -extension Subprocess { +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { /// Terminate the current subprocess with the given exit code /// - Parameter exitCode: The exit code to use for the subprocess. public func terminate(withExitCode exitCode: DWORD) throws { - guard let processHandle = OpenProcess( - // PROCESS_ALL_ACCESS - DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), - false, - self.processIdentifier.processID - ) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + guard + let processHandle = OpenProcess( + // PROCESS_ALL_ACCESS + DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), + false, + self.processIdentifier.value + ) + else { + throw SubprocessError( + code: .init(.failedToTerminate), + underlyingError: .init(rawValue: GetLastError()) ) } defer { CloseHandle(processHandle) } guard TerminateProcess(processHandle, exitCode) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.failedToTerminate), + underlyingError: .init(rawValue: GetLastError()) ) } } /// Suspend the current subprocess public func suspend() throws { - guard let processHandle = OpenProcess( - // PROCESS_ALL_ACCESS - DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), - false, - self.processIdentifier.processID - ) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + guard + let processHandle = OpenProcess( + // PROCESS_ALL_ACCESS + DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), + false, + self.processIdentifier.value + ) + else { + throw SubprocessError( + code: .init(.failedToSuspend), + underlyingError: .init(rawValue: GetLastError()) ) } defer { CloseHandle(processHandle) } - let NTSuspendProcess: Optional<(@convention(c) (HANDLE) -> LONG)> = + let NTSuspendProcess: (@convention(c) (HANDLE) -> LONG)? = unsafeBitCast( GetProcAddress( GetModuleHandleA("ntdll.dll"), @@ -534,53 +542,61 @@ extension Subprocess { to: Optional<(@convention(c) (HANDLE) -> LONG)>.self ) guard let NTSuspendProcess = NTSuspendProcess else { - throw CocoaError(.executableNotLoadable) + throw SubprocessError( + code: .init(.failedToSuspend), + underlyingError: .init(rawValue: GetLastError()) + ) } guard NTSuspendProcess(processHandle) >= 0 else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.failedToSuspend), + underlyingError: .init(rawValue: GetLastError()) ) } } /// Resume the current subprocess after suspension public func resume() throws { - guard let processHandle = OpenProcess( - // PROCESS_ALL_ACCESS - DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), - false, - self.processIdentifier.processID - ) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + guard + let processHandle = OpenProcess( + // PROCESS_ALL_ACCESS + DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), + false, + self.processIdentifier.value + ) + else { + throw SubprocessError( + code: .init(.failedToResume), + underlyingError: .init(rawValue: GetLastError()) ) } defer { CloseHandle(processHandle) } - let NTResumeProcess: Optional<(@convention(c) (HANDLE) -> LONG)> = - unsafeBitCast( - GetProcAddress( - GetModuleHandleA("ntdll.dll"), - "NtResumeProcess" - ), - to: Optional<(@convention(c) (HANDLE) -> LONG)>.self - ) + let NTResumeProcess: (@convention(c) (HANDLE) -> LONG)? = + unsafeBitCast( + GetProcAddress( + GetModuleHandleA("ntdll.dll"), + "NtResumeProcess" + ), + to: Optional<(@convention(c) (HANDLE) -> LONG)>.self + ) guard let NTResumeProcess = NTResumeProcess else { - throw CocoaError(.executableNotLoadable) + throw SubprocessError( + code: .init(.failedToResume), + underlyingError: .init(rawValue: GetLastError()) + ) } guard NTResumeProcess(processHandle) >= 0 else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.failedToResume), + underlyingError: .init(rawValue: GetLastError()) ) } } - internal func tryTerminate() -> Error? { + internal func tryTerminate() -> Swift.Error? { do { try self.terminate(withExitCode: 0) } catch { @@ -591,35 +607,44 @@ extension Subprocess { } // MARK: - Executable Searching -extension Subprocess.Executable { +extension Executable { // Technically not needed for CreateProcess since // it takes process name. It's here to support // Executable.resolveExecutablePath - internal func resolveExecutablePath(withPathValue pathValue: String?) -> String? { + internal func resolveExecutablePath(withPathValue pathValue: String?) throws -> String { switch self.storage { case .executable(let executableName): - return executableName.withCString( + return try executableName.withCString( encodedAs: UTF16.self - ) { exeName -> String? in - return pathValue.withOptionalCString( + ) { exeName -> String in + return try pathValue.withOptionalCString( encodedAs: UTF16.self - ) { path -> String? in + ) { path -> String in let pathLenth = SearchPathW( path, exeName, - nil, 0, nil, nil + nil, + 0, + nil, + nil ) guard pathLenth > 0 else { - return nil + throw SubprocessError( + code: .init(.executableNotFound(executableName)), + underlyingError: .init(rawValue: GetLastError()) + ) } return withUnsafeTemporaryAllocation( - of: WCHAR.self, capacity: Int(pathLenth) + 1 + of: WCHAR.self, + capacity: Int(pathLenth) + 1 ) { _ = SearchPathW( path, - exeName, nil, + exeName, + nil, pathLenth + 1, - $0.baseAddress, nil + $0.baseAddress, + nil ) return String(decodingCString: $0.baseAddress!, as: UTF16.self) } @@ -633,49 +658,60 @@ extension Subprocess.Executable { } // MARK: - Environment Resolution -extension Subprocess.Environment { - internal static let pathEnvironmentVariableName = "Path" +extension Environment { + internal static let pathVariableName = "Path" internal func pathValue() -> String? { switch self.config { case .inherit(let overrides): // If PATH value exists in overrides, use it - if let value = overrides[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = overrides[Self.pathVariableName] { + return value } // Fall back to current process - return ProcessInfo.processInfo.environment[Self.pathEnvironmentVariableName] + return Self.currentEnvironmentValues()[Self.pathVariableName] case .custom(let fullEnvironment): - if let value = fullEnvironment[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = fullEnvironment[Self.pathVariableName] { + return value } return nil } } + + internal static func withCopiedEnv(_ body: ([UnsafeMutablePointer]) -> R) -> R { + var values: [UnsafeMutablePointer] = [] + guard let pwszEnvironmentBlock = GetEnvironmentStringsW() else { + return body([]) + } + defer { FreeEnvironmentStringsW(pwszEnvironmentBlock) } + + var pwszEnvironmentEntry: LPWCH? = pwszEnvironmentBlock + while let value = pwszEnvironmentEntry { + let entry = String(decodingCString: value, as: UTF16.self) + if entry.isEmpty { break } + values.append(entry.withCString { _strdup($0)! }) + pwszEnvironmentEntry = pwszEnvironmentEntry?.advanced(by: wcslen(value) + 1) + } + defer { values.forEach { free($0) } } + return body(values) + } } // MARK: - ProcessIdentifier -extension Subprocess { - /// A platform independent identifier for a subprocess. - public struct ProcessIdentifier: Sendable, Hashable, Codable { - /// Windows specifc process identifier value - public let processID: DWORD - /// Windows specific thread identifier associated with process - public let threadID: DWORD - - internal init( - processID: DWORD, - threadID: DWORD - ) { - self.processID = processID - self.threadID = threadID - } + +/// A platform independent identifier for a subprocess. +public struct ProcessIdentifier: Sendable, Hashable, Codable { + /// Windows specifc process identifier value + public let value: DWORD + + internal init(value: DWORD) { + self.value = value } } -extension Subprocess.ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { +extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { public var description: String { - return "(processID: \(self.processID), threadID: \(self.threadID))" + return "(processID: \(self.value))" } public var debugDescription: String { @@ -684,7 +720,7 @@ extension Subprocess.ProcessIdentifier: CustomStringConvertible, CustomDebugStri } // MARK: - Private Utils -extension Subprocess.Configuration { +extension Configuration { private func preSpawn() throws -> ( applicationName: String?, commandAndArgs: String, @@ -692,44 +728,33 @@ extension Subprocess.Configuration { intendedWorkingDir: String ) { // Prepare environment - var env: [String : String] = [:] + var env: [String: String] = [:] switch self.environment.config { case .custom(let customValues): // Use the custom values directly - for customKey in customValues.keys { - guard case .string(let stringKey) = customKey, - let valueContainer = customValues[customKey], - case .string(let stringValue) = valueContainer else { - fatalError("Windows does not support non unicode String as environments") - } - env.updateValue(stringValue, forKey: stringKey) - } + env = customValues case .inherit(let updateValues): // Combine current environment - env = ProcessInfo.processInfo.environment - for updatingKey in updateValues.keys { - // Override the current environment values - guard case .string(let stringKey) = updatingKey, - let valueContainer = updateValues[updatingKey], - case .string(let stringValue) = valueContainer else { - fatalError("Windows does not support non unicode String as environments") - } - env.updateValue(stringValue, forKey: stringKey) + env = Environment.currentEnvironmentValues() + for (key, value) in updateValues { + env.updateValue(value, forKey: key) } } // On Windows, the PATH is required in order to locate dlls needed by // the process so we should also pass that to the child - let pathVariableName = Subprocess.Environment.pathEnvironmentVariableName + let pathVariableName = Environment.pathVariableName if env[pathVariableName] == nil, - let parentPath = ProcessInfo.processInfo.environment[pathVariableName] { + let parentPath = Environment.currentEnvironmentValues()[pathVariableName] + { env[pathVariableName] = parentPath } // The environment string must be terminated by a double // null-terminator. Otherwise, CreateProcess will fail with // INVALID_PARMETER. - let environmentString = env.map { - $0.key + "=" + $0.value - }.joined(separator: "\0") + "\0\0" + let environmentString = + env.map { + $0.key + "=" + $0.value + }.joined(separator: "\0") + "\0\0" // Prepare arguments let ( @@ -738,9 +763,12 @@ extension Subprocess.Configuration { ) = try self.generateWindowsCommandAndAgruments() // Validate workingDir guard Self.pathAccessible(self.workingDirectory.string) else { - throw CocoaError(.fileNoSuchFile, userInfo: [ - .debugDescriptionErrorKey : "Failed to set working directory to \(self.workingDirectory)" - ]) + throw SubprocessError( + code: .init( + .failedToChangeWorkingDirectory(self.workingDirectory.string) + ), + underlyingError: nil + ) } return ( applicationName: applicationName, @@ -767,9 +795,9 @@ extension Subprocess.Configuration { } private func generateStartupInfo( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput + withInput input: CreatedPipe, + output: CreatedPipe, + error: CreatedPipe ) throws -> STARTUPINFOW { var info: STARTUPINFOW = STARTUPINFOW() info.cb = DWORD(MemoryLayout.size) @@ -781,10 +809,10 @@ extension Subprocess.Configuration { } // Bind IOs // Input - if let inputRead = input.getReadFileDescriptor() { + if let inputRead = input.readFileDescriptor { info.hStdInput = inputRead.platformDescriptor } - if let inputWrite = input.getWriteFileDescriptor() { + if let inputWrite = input.writeFileDescriptor { // Set parent side to be uninhertable SetHandleInformation( inputWrite.platformDescriptor, @@ -793,10 +821,10 @@ extension Subprocess.Configuration { ) } // Output - if let outputWrite = output.getWriteFileDescriptor() { + if let outputWrite = output.writeFileDescriptor { info.hStdOutput = outputWrite.platformDescriptor } - if let outputRead = output.getReadFileDescriptor() { + if let outputRead = output.readFileDescriptor { // Set parent side to be uninhertable SetHandleInformation( outputRead.platformDescriptor, @@ -805,10 +833,10 @@ extension Subprocess.Configuration { ) } // Error - if let errorWrite = error.getWriteFileDescriptor() { + if let errorWrite = error.writeFileDescriptor { info.hStdError = errorWrite.platformDescriptor } - if let errorRead = error.getReadFileDescriptor() { + if let errorRead = error.readFileDescriptor { // Set parent side to be uninhertable SetHandleInformation( errorRead.platformDescriptor, @@ -834,12 +862,12 @@ extension Subprocess.Configuration { // actually resolve the path. However, to maintain // the same behavior as other platforms, still check // here to make sure the executable actually exists - guard self.executable.resolveExecutablePath( - withPathValue: self.environment.pathValue() - ) != nil else { - throw CocoaError(.executableNotLoadable, userInfo: [ - .debugDescriptionErrorKey : "\(self.executable.description) is not an executable" - ]) + do { + _ = try self.executable.resolveExecutablePath( + withPathValue: self.environment.pathValue() + ) + } catch { + throw error } executableNameOrPath = name } @@ -876,7 +904,7 @@ extension Subprocess.Configuration { func quoteWindowsCommandArg(arg: String) -> String { // Windows escaping, adapted from Daniel Colascione's "Everyone quotes // command line arguments the wrong way" - Microsoft Developer Blog - if !arg.contains(where: {" \t\n\"".contains($0)}) { + if !arg.contains(where: { " \t\n\"".contains($0) }) { return arg } @@ -911,7 +939,7 @@ extension Subprocess.Configuration { break } let backslashCount = unquoted.distance(from: unquoted.startIndex, to: firstNonBackslash) - if (unquoted[firstNonBackslash] == "\"") { + if unquoted[firstNonBackslash] == "\"" { // This is a string of \ followed by a " e.g. foo\"bar. Escape the // backslashes and the quote quoted.append(String(repeating: "\\", count: backslashCount * 2 + 1)) @@ -939,20 +967,7 @@ extension Subprocess.Configuration { } // MARK: - PlatformFileDescriptor Type -extension Subprocess { - internal typealias PlatformFileDescriptor = HANDLE -} - -// MARK: - Read Buffer Size -extension Subprocess { - @inline(__always) - internal static var readBufferSize: Int { - // FIXME: Use Platform.pageSize here - var sysInfo: SYSTEM_INFO = SYSTEM_INFO() - GetSystemInfo(&sysInfo) - return Int(sysInfo.dwPageSize) - } -} +internal typealias PlatformFileDescriptor = HANDLE // MARK: - Pipe Support extension FileDescriptor { @@ -968,13 +983,14 @@ extension FileDescriptor { var readHandle: HANDLE? = nil var writeHandle: HANDLE? = nil guard CreatePipe(&readHandle, &writeHandle, &saAttributes, 0), - readHandle != INVALID_HANDLE_VALUE, - writeHandle != INVALID_HANDLE_VALUE, - let readHandle: HANDLE = readHandle, - let writeHandle: HANDLE = writeHandle else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileReadUnknown + readHandle != INVALID_HANDLE_VALUE, + writeHandle != INVALID_HANDLE_VALUE, + let readHandle: HANDLE = readHandle, + let writeHandle: HANDLE = writeHandle + else { + throw SubprocessError( + code: .init(.failedToCreatePipe), + underlyingError: .init(rawValue: GetLastError()) ) } let readFd = _open_osfhandle( @@ -992,147 +1008,138 @@ extension FileDescriptor { ) } - internal static func openDevNull( - withAcessMode mode: FileDescriptor.AccessMode - ) throws -> FileDescriptor { - return try "NUL".withPlatformString { - let handle = CreateFileW( - $0, - DWORD(GENERIC_WRITE), - DWORD(FILE_SHARE_WRITE), - nil, - DWORD(OPEN_EXISTING), - DWORD(FILE_ATTRIBUTE_NORMAL), - nil - ) - guard let handle = handle, - handle != INVALID_HANDLE_VALUE else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileReadUnknown - ) - } - let devnull = _open_osfhandle( - intptr_t(bitPattern: handle), - mode.rawValue - ) - return FileDescriptor(rawValue: devnull) - } - } - - var platformDescriptor: Subprocess.PlatformFileDescriptor { + var platformDescriptor: PlatformFileDescriptor { return HANDLE(bitPattern: _get_osfhandle(self.rawValue))! } - internal func read(upToLength maxLength: Int) async throws -> Data { - // TODO: Figure out a better way to asynchornously read + internal func readChunk(upToLength maxLength: Int) async throws -> SequenceOutput.Buffer? { return try await withCheckedThrowingContinuation { continuation in - DispatchQueue.global(qos: .userInitiated).async { - var totalBytesRead: Int = 0 - var lastError: DWORD? = nil - let values = Array( - unsafeUninitializedCapacity: maxLength - ) { buffer, initializedCount in - while true { - guard let baseAddress = buffer.baseAddress else { - initializedCount = 0 - break - } - let bufferPtr = baseAddress.advanced(by: totalBytesRead) - var bytesRead: DWORD = 0 - let readSucceed = ReadFile( - self.platformDescriptor, - UnsafeMutableRawPointer(mutating: bufferPtr), - DWORD(maxLength - totalBytesRead), - &bytesRead, - nil - ) - if !readSucceed { - // Windows throws ERROR_BROKEN_PIPE when the pipe is closed - let error = GetLastError() - if error == ERROR_BROKEN_PIPE { - // We are done reading - initializedCount = totalBytesRead - } else { - // We got some error - lastError = error - initializedCount = 0 - } - break - } else { - // We succesfully read the current round - totalBytesRead += Int(bytesRead) - } - - if totalBytesRead >= maxLength { - initializedCount = min(maxLength, totalBytesRead) - break - } - } - } - if let lastError = lastError { - continuation.resume(throwing: CocoaError.windowsError( - underlying: lastError, - errorCode: .fileReadUnknown) - ) - } else { - continuation.resume(returning: Data(values)) + self.readUntilEOF( + upToLength: maxLength + ) { result in + switch result { + case .failure(let error): + continuation.resume(throwing: error) + case .success(let bytes): + continuation.resume(returning: SequenceOutput.Buffer(data: bytes)) } } } } - internal func write(_ data: S) async throws where S.Element == UInt8 { - // TODO: Figure out a better way to asynchornously write - try await withCheckedThrowingContinuation { ( - continuation: CheckedContinuation - ) -> Void in - DispatchQueue.global(qos: .userInitiated).async { - let buffer = Array(data) - buffer.withUnsafeBytes { ptr in - var writtenBytes: DWORD = 0 - let writeSucceed = WriteFile( + internal func readUntilEOF( + upToLength maxLength: Int, + resultHandler: @Sendable @escaping (Swift.Result<[UInt8], any (Error & Sendable)>) -> Void + ) { + DispatchQueue.global(qos: .userInitiated).async { + var totalBytesRead: Int = 0 + var lastError: DWORD? = nil + let values = [UInt8]( + unsafeUninitializedCapacity: maxLength + ) { buffer, initializedCount in + while true { + guard let baseAddress = buffer.baseAddress else { + initializedCount = 0 + break + } + let bufferPtr = baseAddress.advanced(by: totalBytesRead) + var bytesRead: DWORD = 0 + let readSucceed = ReadFile( self.platformDescriptor, - ptr.baseAddress, - DWORD(buffer.count), - &writtenBytes, + UnsafeMutableRawPointer(mutating: bufferPtr), + DWORD(maxLength - totalBytesRead), + &bytesRead, nil ) - if !writeSucceed { - continuation.resume(throwing: CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown) - ) + if !readSucceed { + // Windows throws ERROR_BROKEN_PIPE when the pipe is closed + let error = GetLastError() + if error == ERROR_BROKEN_PIPE { + // We are done reading + initializedCount = totalBytesRead + } else { + // We got some error + lastError = error + initializedCount = 0 + } + break } else { - continuation.resume() + // We succesfully read the current round + totalBytesRead += Int(bytesRead) + } + + if totalBytesRead >= maxLength { + initializedCount = min(maxLength, totalBytesRead) + break } } } + if let lastError = lastError { + let windowsError = SubprocessError( + code: .init(.failedToReadFromSubprocess), + underlyingError: .init(rawValue: lastError) + ) + resultHandler(.failure(windowsError)) + } else { + resultHandler(.success(values)) + } } } -} - -extension String { - static let debugDescriptionErrorKey = "DebugDescription" -} -// MARK: - CocoaError + Win32 -internal let NSUnderlyingErrorKey = "NSUnderlyingError" + internal func write( + _ array: [UInt8] + ) async throws -> Int { + try await withCheckedThrowingContinuation { continuation in + // TODO: Figure out a better way to asynchornously write + DispatchQueue.global(qos: .userInitiated).async { + array.withUnsafeBytes { + self.write($0) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) + } + } + } + } + } + } -extension CocoaError { - static func windowsError(underlying: DWORD, errorCode: Code) -> CocoaError { - let userInfo = [ - NSUnderlyingErrorKey : Win32Error(underlying) - ] - return CocoaError(errorCode, userInfo: userInfo) + package func write( + _ ptr: UnsafeRawBufferPointer, + completion: @escaping (Int, Swift.Error?) -> Void + ) { + func _write( + _ ptr: UnsafeRawBufferPointer, + count: Int, + completion: @escaping (Int, Swift.Error?) -> Void + ) { + var writtenBytes: DWORD = 0 + let writeSucceed = WriteFile( + self.platformDescriptor, + ptr.baseAddress, + DWORD(count), + &writtenBytes, + nil + ) + if !writeSucceed { + let error = SubprocessError( + code: .init(.failedToWriteToSubprocess), + underlyingError: .init(rawValue: GetLastError()) + ) + completion(Int(writtenBytes), error) + } else { + completion(Int(writtenBytes), nil) + } + } } } -private extension Optional where Wrapped == String { - func withOptionalCString( +extension Optional where Wrapped == String { + fileprivate func withOptionalCString( encodedAs targetEncoding: Encoding.Type, _ body: (UnsafePointer?) throws -> Result - ) rethrows -> Result where Encoding : _UnicodeEncoding { + ) rethrows -> Result where Encoding: _UnicodeEncoding { switch self { case .none: return try body(nil) @@ -1141,7 +1148,7 @@ private extension Optional where Wrapped == String { } } - func withOptionalNTPathRepresentation( + fileprivate func withOptionalNTPathRepresentation( _ body: (UnsafePointer?) throws -> Result ) throws -> Result { switch self { @@ -1159,23 +1166,30 @@ extension String { _ body: (UnsafePointer) throws -> Result ) throws -> Result { guard !isEmpty else { - throw CocoaError(.fileReadInvalidFileName) + throw SubprocessError( + code: .init(.invalidWindowsPath(self)), + underlyingError: nil + ) } var iter = self.utf8.makeIterator() - let bLeadingSlash = if [._slash, ._backslash].contains(iter.next()), iter.next()?.isLetter ?? false, iter.next() == ._colon { true } else { false } + let bLeadingSlash = + if [._slash, ._backslash].contains(iter.next()), iter.next()?.isLetter ?? false, iter.next() == ._colon { + true + } else { false } // Strip the leading `/` on a RFC8089 path (`/[drive-letter]:/...` ). A // leading slash indicates a rooted path on the drive for the current // working directory. - return try Substring(self.utf8.dropFirst(bLeadingSlash ? 1 : 0)).withCString(encodedAs: UTF16.self) { pwszPath in + return try Substring(self.utf8.dropFirst(bLeadingSlash ? 1 : 0)).withCString(encodedAs: UTF16.self) { + pwszPath in // 1. Normalize the path first. let dwLength: DWORD = GetFullPathNameW(pwszPath, 0, nil, nil) return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { guard GetFullPathNameW(pwszPath, DWORD($0.count), $0.baseAddress, nil) > 0 else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.invalidWindowsPath(self)), + underlyingError: .init(rawValue: GetLastError()) ) } @@ -1186,27 +1200,23 @@ extension String { } } -struct Win32Error: Error { - public typealias Code = DWORD - public let code: Code - - public static var errorDomain: String { - return "NSWin32ErrorDomain" - } - - public init(_ code: Code) { - self.code = code - } -} - -internal extension UInt8 { +extension UInt8 { static var _slash: UInt8 { UInt8(ascii: "/") } static var _backslash: UInt8 { UInt8(ascii: "\\") } static var _colon: UInt8 { UInt8(ascii: ":") } var isLetter: Bool? { - return (0x41 ... 0x5a) ~= self || (0x61 ... 0x7a) ~= self + return (0x41...0x5a) ~= self || (0x61...0x7a) ~= self + } +} + +extension OutputProtocol { + internal func output(from data: [UInt8]) throws -> OutputType { + return try data.withUnsafeBytes { + let span = RawSpan(_unsafeBytes: $0) + return try self.output(from: span) + } } } -#endif // canImport(WinSDK) +#endif // canImport(WinSDK) diff --git a/Sources/_Subprocess/Result.swift b/Sources/_Subprocess/Result.swift new file mode 100644 index 00000000..e1f79894 --- /dev/null +++ b/Sources/_Subprocess/Result.swift @@ -0,0 +1,123 @@ +//===----------------------------------------------------------------------===// +// +// 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: - Result + +/// A simple wrapper around the generic result returned by the +/// `run` closures with the corresponding `TerminationStatus` +/// of the child process. +public struct ExecutionResult { + /// The termination status of the child process + public let terminationStatus: TerminationStatus + /// The result returned by the closure passed to `.run` methods + public let value: Result + + internal init(terminationStatus: TerminationStatus, value: Result) { + self.terminationStatus = terminationStatus + self.value = value + } +} + +/// The result of a subprocess execution with its collected +/// standard output and standard error. +public struct CollectedResult< + Output: OutputProtocol, + Error: OutputProtocol +>: Sendable { + /// The process identifier for the executed subprocess + public let processIdentifier: ProcessIdentifier + /// The termination status of the executed subprocess + public let terminationStatus: TerminationStatus + public let standardOutput: Output.OutputType + public let standardError: Error.OutputType + + internal init( + processIdentifier: ProcessIdentifier, + terminationStatus: TerminationStatus, + standardOutput: Output.OutputType, + standardError: Error.OutputType + ) { + self.processIdentifier = processIdentifier + self.terminationStatus = terminationStatus + self.standardOutput = standardOutput + self.standardError = standardError + } +} + +// MARK: - CollectedResult Conformances +extension CollectedResult: Equatable where Output.OutputType: Equatable, Error.OutputType: Equatable {} + +extension CollectedResult: Hashable where Output.OutputType: Hashable, Error.OutputType: Hashable {} + +extension CollectedResult: Codable where Output.OutputType: Codable, Error.OutputType: Codable {} + +extension CollectedResult: CustomStringConvertible +where Output.OutputType: CustomStringConvertible, Error.OutputType: CustomStringConvertible { + public var description: String { + return """ + CollectedResult( + processIdentifier: \(self.processIdentifier), + terminationStatus: \(self.terminationStatus.description), + standardOutput: \(self.standardOutput.description) + standardError: \(self.standardError.description) + ) + """ + } +} + +extension CollectedResult: CustomDebugStringConvertible +where Output.OutputType: CustomDebugStringConvertible, Error.OutputType: CustomDebugStringConvertible { + public var debugDescription: String { + return """ + CollectedResult( + processIdentifier: \(self.processIdentifier), + terminationStatus: \(self.terminationStatus.description), + standardOutput: \(self.standardOutput.debugDescription) + standardError: \(self.standardError.debugDescription) + ) + """ + } +} + +// MARK: - ExecutionResult Conformances +extension ExecutionResult: Equatable where Result: Equatable {} + +extension ExecutionResult: Hashable where Result: Hashable {} + +extension ExecutionResult: Codable where Result: Codable {} + +extension ExecutionResult: CustomStringConvertible where Result: CustomStringConvertible { + public var description: String { + return """ + ExecutionResult( + terminationStatus: \(self.terminationStatus.description), + value: \(self.value.description) + ) + """ + } +} + +extension ExecutionResult: CustomDebugStringConvertible where Result: CustomDebugStringConvertible { + public var debugDescription: String { + return """ + ExecutionResult( + terminationStatus: \(self.terminationStatus.debugDescription), + value: \(self.value.debugDescription) + ) + """ + } +} diff --git a/Sources/_Subprocess/Subprocess+API.swift b/Sources/_Subprocess/Subprocess+API.swift deleted file mode 100644 index f9c8b1ec..00000000 --- a/Sources/_Subprocess/Subprocess+API.swift +++ /dev/null @@ -1,465 +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 SystemPackage -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - /// Run a executable with given parameters and capture its - /// standard output and standard error. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory to use for the subprocess. - /// - 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 collecting the standard output. - /// - error: The method to use for collecting the standard error. - /// - Returns: `CollectedResult` which contains process identifier, - /// termination status, captured standard output and standard error. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: InputMethod = .noInput, - output: CollectedOutputMethod = .collect(), - error: CollectedOutputMethod = .collect() - ) async throws -> CollectedResult { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - input: input, - output: .init(method: output.method), - error: .init(method: error.method) - ) { subprocess in - let (standardOutput, standardError) = try await subprocess.captureIOs() - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: standardOutput, - standardError: standardError - ) - } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } - - /// Run a executable with given parameters and capture its - /// standard output and standard error. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory to use for the subprocess. - /// - 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 collecting the standard output. - /// - error: The method to use for collecting the standard error. - /// - Returns: `CollectedResult` which contains process identifier, - /// termination status, captured standard output and standard error. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: some Sequence, - output: CollectedOutputMethod = .collect(), - error: CollectedOutputMethod = .collect() - ) async throws -> CollectedResult { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - output: .init(method: output.method), - error: .init(method: output.method) - ) { subprocess, writer in - return try await withThrowingTaskGroup(of: CapturedIOs?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await subprocess.captureIOs() - } - var capturedIOs: CapturedIOs! - while let result = try await group.next() { - if result != nil { - capturedIOs = result - } - } - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: capturedIOs.standardOutput, - standardError: capturedIOs.standardError - ) - } - } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } - - /// Run a executable with given parameters and capture its - /// standard output and standard error. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory to use for the subprocess. - /// - 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 collecting the standard output. - /// - error: The method to use for collecting the standard error. - /// - Returns: `CollectedResult` which contains process identifier, - /// termination status, captured standard output and standard error. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: S, - output: CollectedOutputMethod = .collect(), - error: CollectedOutputMethod = .collect() - ) async throws -> CollectedResult where S.Element == UInt8 { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - output: .init(method: output.method), - error: .init(method: output.method) - ) { subprocess, writer in - return try await withThrowingTaskGroup(of: CapturedIOs?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await subprocess.captureIOs() - } - var capturedIOs: CapturedIOs! - while let result = try await group.next() { - capturedIOs = result - } - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: capturedIOs.standardOutput, - standardError: capturedIOs.standardError - ) - } - } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } -} - -// MARK: Custom Execution Body -extension Subprocess { - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and its IOs. - /// - 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. - /// - body: The custom execution body to manually control the running process - /// - Returns a ExecutableResult type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: InputMethod = .noInput, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(input: input, output: output, error: error, body) - } - - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and its IOs. - /// - 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. - /// - body: The custom execution body to manually control the running process - /// - Returns a `ExecutableResult` type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: some Sequence, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error) { execution, writer in - return try await withThrowingTaskGroup(of: R?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await body(execution) - } - var result: R! - while let next = try await group.next() { - result = next - } - return result - } - } - } - - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and its IOs. - /// - 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. - /// - body: The custom execution body to manually control the running process - /// - Returns a `ExecutableResult` type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: S, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult where S.Element == UInt8 { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error) { execution, writer in - return try await withThrowingTaskGroup(of: R?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await body(execution) - } - var result: R! - while let next = try await group.next() { - result = next - } - return result - } - } - } - - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and write to its - /// standard input via `StandardInputWriter` - /// - 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. - /// - output: The method to use for redirecting the standard output. - /// - error: The method to use for redirecting the standard error. - /// - body: The custom execution body to manually control the running process - /// - Returns a ExecutableResult type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess, StandardInputWriter) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error, body) - } -} - -// MARK: - Configuration Based -extension Subprocess { - /// Run a executable with given parameters specified by a - /// `Subprocess.Configuration` - /// - Parameters: - /// - configuration: The `Subprocess` configuration to run. - /// - output: The method to use for redirecting the standard output. - /// - error: The method to use for redirecting the standard error. - /// - body: The custom configuration body to manually control - /// the running process and write to its standard input. - /// - Returns a ExecutableResult type containing the return value - /// of the closure. - public static func run( - _ configuration: Configuration, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess, StandardInputWriter) async throws -> R) - ) async throws -> ExecutionResult { - return try await configuration.run(output: output, error: error, body) - } -} - -// MARK: - Detached -extension Subprocess { - /// Run a executable with given parameters and return its process - /// identifier immediately without monitoring the state of the - /// subprocess nor waiting until it exits. - /// - /// This method is useful for launching subprocesses that outlive their - /// parents (for example, daemons and trampolines). - /// - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory for the process. - /// - platformOptions: The platform specific options to use for the process. - /// - input: A file descriptor to bind to the subprocess' standard input. - /// - output: A file descriptor to bind to the subprocess' standard output. - /// - error: A file descriptor to bind to the subprocess' standard error. - /// - Returns: the process identifier for the subprocess. - public static func runDetached( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: FileDescriptor? = nil, - output: FileDescriptor? = nil, - error: FileDescriptor? = nil - ) throws -> ProcessIdentifier { - // Create input - let executionInput: ExecutionInput - let executionOutput: ExecutionOutput - let executionError: ExecutionOutput - if let inputFd = input { - executionInput = .init(storage: .fileDescriptor(inputFd, false)) - } else { - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) - executionInput = .init(storage: .noInput(devnull)) - } - if let outputFd = output { - executionOutput = .init(storage: .fileDescriptor(outputFd, false)) - } else { - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - executionOutput = .init(storage: .discarded(devnull)) - } - if let errorFd = error { - executionError = .init( - storage: .fileDescriptor(errorFd, false) - ) - } else { - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - executionError = .init(storage: .discarded(devnull)) - } - // Spawn! - let config: Configuration = Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - return try config.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError - ).processIdentifier - } -} - diff --git a/Sources/_Subprocess/Subprocess+AsyncDataSequence.swift b/Sources/_Subprocess/Subprocess+AsyncDataSequence.swift deleted file mode 100644 index a3a3e393..00000000 --- a/Sources/_Subprocess/Subprocess+AsyncDataSequence.swift +++ /dev/null @@ -1,86 +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 SystemPackage -import Dispatch - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - public struct AsyncDataSequence: AsyncSequence, Sendable, _AsyncSequence { - public typealias Error = any Swift.Error - - public typealias Element = Data - - @_nonSendable - public struct Iterator: AsyncIteratorProtocol { - public typealias Element = Data - - private let fileDescriptor: FileDescriptor - private var buffer: [UInt8] - private var currentPosition: Int - private var finished: Bool - - internal init(fileDescriptor: FileDescriptor) { - self.fileDescriptor = fileDescriptor - self.buffer = [] - self.currentPosition = 0 - self.finished = false - } - - public mutating func next() async throws -> Data? { - let data = try await self.fileDescriptor.readChunk( - upToLength: Subprocess.readBufferSize - ) - if data == nil { - // We finished reading. Close the file descriptor now - try self.fileDescriptor.close() - return nil - } - return data - } - } - - private let fileDescriptor: FileDescriptor - - init(fileDescriptor: FileDescriptor) { - self.fileDescriptor = fileDescriptor - } - - public func makeAsyncIterator() -> Iterator { - return Iterator(fileDescriptor: self.fileDescriptor) - } - } -} - -extension RangeReplaceableCollection { - /// Creates a new instance of a collection containing the elements of an asynchronous sequence. - /// - /// - Parameter source: The asynchronous sequence of elements for the new collection. - @inlinable - public init(_ source: Source) async rethrows where Source.Element == Element { - self.init() - for try await item in source { - append(item) - } - } -} - -public protocol _AsyncSequence: AsyncSequence { - associatedtype Error -} diff --git a/Sources/_Subprocess/Subprocess+Configuration.swift b/Sources/_Subprocess/Subprocess+Configuration.swift deleted file mode 100644 index 0b785790..00000000 --- a/Sources/_Subprocess/Subprocess+Configuration.swift +++ /dev/null @@ -1,769 +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 -// -//===----------------------------------------------------------------------===// - -@preconcurrency import SystemPackage - -#if canImport(Darwin) -import Darwin -#elseif canImport(Glibc) -import Glibc -#elseif canImport(WinSDK) -import WinSDK -#endif - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - /// A collection of configurations parameters to use when - /// spawning a subprocess. - public struct Configuration: Sendable, Hashable { - - internal enum RunState: Sendable { - case workBody(Result) - case monitorChildProcess(TerminationStatus) - } - - /// The executable to run. - public var executable: Executable - /// The arguments to pass to the executable. - public var arguments: Arguments - /// The environment to use when running the executable. - public var environment: Environment - /// The working directory to use when running the executable. - public var workingDirectory: FilePath - /// The platform specifc options to use when - /// running the subprocess. - public var platformOptions: PlatformOptions - - public init( - executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions() - ) { - self.executable = executable - self.arguments = arguments - self.environment = environment - self.workingDirectory = workingDirectory ?? .currentWorkingDirectory - self.platformOptions = platformOptions - } - - /// Close each input individually, and throw the first error if there's multiple errors thrown - @Sendable - private func cleanup( - process: Subprocess, - childSide: Bool, parentSide: Bool, - attemptToTerminateSubProcess: Bool - ) async throws { - guard childSide || parentSide || attemptToTerminateSubProcess else { - return - } - - // Attempt to teardown the subprocess - if attemptToTerminateSubProcess { - await process.teardown( - using: self.platformOptions.teardownSequence - ) - } - - let inputCloseFunc: () throws -> Void - let outputCloseFunc: () throws -> Void - let errorCloseFunc: () throws -> Void - if childSide && parentSide { - // Close all - inputCloseFunc = process.executionInput.closeAll - outputCloseFunc = process.executionOutput.closeAll - errorCloseFunc = process.executionError.closeAll - } else if childSide { - // Close child only - inputCloseFunc = process.executionInput.closeChildSide - outputCloseFunc = process.executionOutput.closeChildSide - errorCloseFunc = process.executionError.closeChildSide - } else { - // Close parent only - inputCloseFunc = process.executionInput.closeParentSide - outputCloseFunc = process.executionOutput.closeParentSide - errorCloseFunc = process.executionError.closeParentSide - } - - var inputError: Error? - var outputError: Error? - var errorError: Error? // lol - do { - try inputCloseFunc() - } catch { - inputError = error - } - - do { - try outputCloseFunc() - } catch { - outputError = error - } - - do { - try errorCloseFunc() - } catch { - errorError = error // lolol - } - - if let inputError = inputError { - throw inputError - } - - if let outputError = outputError { - throw outputError - } - - if let errorError = errorError { - throw errorError - } - } - - /// Close each input individually, and throw the first error if there's multiple errors thrown - @Sendable - internal func cleanupAll( - input: ExecutionInput, - output: ExecutionOutput, - error: ExecutionOutput - ) throws { - var inputError: Error? - var outputError: Error? - var errorError: Error? - - do { - try input.closeAll() - } catch { - inputError = error - } - - do { - try output.closeAll() - } catch { - outputError = error - } - - do { - try error.closeAll() - } catch { - errorError = error - } - - if let inputError = inputError { - throw inputError - } - if let outputError = outputError { - throw outputError - } - if let errorError = errorError { - throw errorError - } - } - - internal func run( - output: RedirectedOutputMethod, - error: RedirectedOutputMethod, - _ body: sending @escaping (Subprocess, StandardInputWriter) async throws -> R - ) async throws -> ExecutionResult { - let (readFd, writeFd) = try FileDescriptor.pipe() - let executionInput: ExecutionInput = .init(storage: .customWrite(readFd, writeFd)) - let executionOutput: ExecutionOutput = try output.createExecutionOutput() - let executionError: ExecutionOutput = try error.createExecutionOutput() - let process: Subprocess = try self.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError) - // After spawn, cleanup child side fds - try await self.cleanup( - process: process, - childSide: true, - parentSide: false, - attemptToTerminateSubProcess: false - ) - return try await withAsyncTaskCancellationHandler { - return try await withThrowingTaskGroup(of: RunState.self) { group in - group.addTask { - let status = try await monitorProcessTermination( - forProcessWithIdentifier: process.processIdentifier) - return .monitorChildProcess(status) - } - group.addTask { - do { - let result = try await body(process, .init(input: executionInput)) - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - return .workBody(result) - } catch { - // Cleanup everything - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - throw error - } - } - - var result: R! - var terminationStatus: TerminationStatus! - while let state = try await group.next() { - switch state { - case .monitorChildProcess(let status): - // We don't really care about termination status here - terminationStatus = status - case .workBody(let workResult): - result = workResult - } - } - return ExecutionResult(terminationStatus: terminationStatus, value: result) - } - } onCancel: { - // Attempt to terminate the child process - // Since the task has already been cancelled, - // this is the best we can do - try? await self.cleanup( - process: process, - childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - } - } - - internal func run( - input: InputMethod, - output: RedirectedOutputMethod, - error: RedirectedOutputMethod, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - let executionInput = try input.createExecutionInput() - let executionOutput = try output.createExecutionOutput() - let executionError = try error.createExecutionOutput() - let process = try self.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError) - // After spawn, clean up child side - try await self.cleanup( - process: process, - childSide: true, - parentSide: false, - attemptToTerminateSubProcess: false - ) - return try await withAsyncTaskCancellationHandler { - return try await withThrowingTaskGroup(of: RunState.self) { group in - group.addTask { - let status = try await monitorProcessTermination( - forProcessWithIdentifier: process.processIdentifier) - return .monitorChildProcess(status) - } - group.addTask { - do { - let result = try await body(process) - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - return .workBody(result) - } catch { - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - throw error - } - } - - var result: R! - var terminationStatus: TerminationStatus! - while let state = try await group.next() { - switch state { - case .monitorChildProcess(let status): - terminationStatus = status - case .workBody(let workResult): - result = workResult - } - } - return ExecutionResult(terminationStatus: terminationStatus, value: result) - } - } onCancel: { - // Attempt to terminate the child process - // Since the task has already been cancelled, - // this is the best we can do - try? await self.cleanup( - process: process, - childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - } - } - } -} - -extension Subprocess.Configuration : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - return """ -Subprocess.Configuration( - executable: \(self.executable.description), - arguments: \(self.arguments.description), - environment: \(self.environment.description), - workingDirectory: \(self.workingDirectory), - platformOptions: \(self.platformOptions.description(withIndent: 1)) -) -""" - } - - public var debugDescription: String { - return """ -Subprocess.Configuration( - executable: \(self.executable.debugDescription), - arguments: \(self.arguments.debugDescription), - environment: \(self.environment.debugDescription), - workingDirectory: \(self.workingDirectory), - platformOptions: \(self.platformOptions.description(withIndent: 1)) -) -""" - } -} - -// MARK: - Executable -extension Subprocess { - /// `Subprocess.Executable` defines how should the executable - /// be looked up for execution. - public struct Executable: Sendable, Hashable { - internal enum Configuration: Sendable, Hashable { - case executable(String) - case path(FilePath) - } - - internal let storage: Configuration - - private init(_config: Configuration) { - self.storage = _config - } - - /// Locate the executable by its name. - /// `Subprocess` will use `PATH` value to - /// determine the full path to the executable. - public static func named(_ executableName: String) -> Self { - return .init(_config: .executable(executableName)) - } - /// Locate the executable by its full path. - /// `Subprocess` will use this path directly. - public static func at(_ filePath: FilePath) -> Self { - return .init(_config: .path(filePath)) - } - /// Returns the full executable path given the environment value. - public func resolveExecutablePath(in environment: Environment) -> FilePath? { - if let path = self.resolveExecutablePath(withPathValue: environment.pathValue()) { - return FilePath(path) - } - return nil - } - } -} - -extension Subprocess.Executable : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - switch storage { - case .executable(let executableName): - return executableName - case .path(let filePath): - return filePath.string - } - } - - public var debugDescription: String { - switch storage { - case .executable(let string): - return "executable(\(string))" - case .path(let filePath): - return "path(\(filePath.string))" - } - } -} - -// MARK: - Arguments -extension Subprocess { - /// A collection of arguments to pass to the subprocess. - public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { - public typealias ArrayLiteralElement = String - - internal let storage: [StringOrRawBytes] - internal let executablePathOverride: StringOrRawBytes? - - /// Create an Arguments object using the given literal values - public init(arrayLiteral elements: String...) { - self.storage = elements.map { .string($0) } - self.executablePathOverride = nil - } - /// Create an Arguments object using the given array - public init(_ array: [String]) { - self.storage = array.map { .string($0) } - self.executablePathOverride = nil - } - -#if !os(Windows) // Windows does NOT support arg0 override - /// Create an `Argument` object using the given values, but - /// override the first Argument value to `executablePathOverride`. - /// If `executablePathOverride` is nil, - /// `Arguments` will automatically use the executable path - /// as the first argument. - /// - Parameters: - /// - executablePathOverride: the value to override the first argument. - /// - remainingValues: the rest of the argument value - public init(executablePathOverride: String?, remainingValues: [String]) { - self.storage = remainingValues.map { .string($0) } - if let executablePathOverride = executablePathOverride { - self.executablePathOverride = .string(executablePathOverride) - } else { - self.executablePathOverride = nil - } - } - - /// Create an `Argument` object using the given values, but - /// override the first Argument value to `executablePathOverride`. - /// If `executablePathOverride` is nil, - /// `Arguments` will automatically use the executable path - /// as the first argument. - /// - Parameters: - /// - executablePathOverride: the value to override the first argument. - /// - remainingValues: the rest of the argument value - public init(executablePathOverride: Data?, remainingValues: [Data]) { - self.storage = remainingValues.map { .rawBytes($0.toArray()) } - if let override = executablePathOverride { - self.executablePathOverride = .rawBytes(override.toArray()) - } else { - self.executablePathOverride = nil - } - } - - public init(_ array: [Data]) { - self.storage = array.map { .rawBytes($0.toArray()) } - self.executablePathOverride = nil - } -#endif - } -} - -extension Subprocess.Arguments : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - var result: [String] = self.storage.map(\.description) - - if let override = self.executablePathOverride { - result.insert("override\(override.description)", at: 0) - } - return result.description - } - - public var debugDescription: String { return self.description } -} - -// MARK: - Environment -extension Subprocess { - /// A set of environment variables to use when executing the subprocess. - public struct Environment: Sendable, Hashable { - internal enum Configuration: Sendable, Hashable { - case inherit([StringOrRawBytes : StringOrRawBytes]) - case custom([StringOrRawBytes : StringOrRawBytes]) - } - - internal let config: Configuration - - init(config: Configuration) { - self.config = config - } - /// Child process should inherit the same environment - /// values from its parent process. - public static var inherit: Self { - return .init(config: .inherit([:])) - } - /// Override the provided `newValue` in the existing `Environment` - public func updating(_ newValue: [String : String]) -> Self { - return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) - } - /// Use custom environment variables - public static func custom(_ newValue: [String : String]) -> Self { - return .init(config: .custom(newValue.wrapToStringOrRawBytes())) - } - -#if !os(Windows) - /// Override the provided `newValue` in the existing `Environment` - public func updating(_ newValue: [Data : Data]) -> Self { - return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) - } - /// Use custom environment variables - public static func custom(_ newValue: [Data : Data]) -> Self { - return .init(config: .custom(newValue.wrapToStringOrRawBytes())) - } -#endif - } -} - -extension Subprocess.Environment : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - switch self.config { - case .custom(let customDictionary): - return customDictionary.dictionaryDescription - case .inherit(let updateValue): - return "Inherting current environment with updates: \(updateValue.dictionaryDescription)" - } - } - - public var debugDescription: String { - return self.description - } -} - -fileprivate extension Dictionary where Key == String, Value == String { - func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes : Subprocess.StringOrRawBytes] { - var result = Dictionary< - Subprocess.StringOrRawBytes, - Subprocess.StringOrRawBytes - >(minimumCapacity: self.count) - for (key, value) in self { - result[.string(key)] = .string(value) - } - return result - } -} - -fileprivate extension Dictionary where Key == Data, Value == Data { - func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes : Subprocess.StringOrRawBytes] { - var result = Dictionary< - Subprocess.StringOrRawBytes, - Subprocess.StringOrRawBytes - >(minimumCapacity: self.count) - for (key, value) in self { - result[.rawBytes(key.toArray())] = .rawBytes(value.toArray()) - } - return result - } -} - -fileprivate extension Dictionary where Key == Subprocess.StringOrRawBytes, Value == Subprocess.StringOrRawBytes { - var dictionaryDescription: String { - var result = "[\n" - for (key, value) in self { - result += "\t\(key.description) : \(value.description),\n" - } - result += "]" - return result - } -} - -fileprivate extension Data { - func toArray() -> [T] { - return self.withUnsafeBytes { ptr in - return Array(ptr.bindMemory(to: T.self)) - } - } -} - -// MARK: - TerminationStatus -extension Subprocess { - /// An exit status of a subprocess. - @frozen - public enum TerminationStatus: Sendable, Hashable, Codable { - #if canImport(WinSDK) - public typealias Code = DWORD - #else - public typealias Code = CInt - #endif - - /// The subprocess was existed with the given code - case exited(Code) - /// The subprocess was signalled with given exception value - case unhandledException(Code) - /// Whether the current TerminationStatus is successful. - public var isSuccess: Bool { - switch self { - case .exited(let exitCode): - return exitCode == 0 - case .unhandledException(_): - return false - } - } - } -} - -extension Subprocess.TerminationStatus : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - switch self { - case .exited(let code): - return "exited(\(code))" - case .unhandledException(let code): - return "unhandledException(\(code))" - } - } - - public var debugDescription: String { - return self.description - } -} - -// MARK: - Internal -extension Subprocess { - internal enum StringOrRawBytes: Sendable, Hashable { - case string(String) - case rawBytes([CChar]) - - // Return value needs to be deallocated manually by callee - func createRawBytes() -> UnsafeMutablePointer { - switch self { - case .string(let string): - return strdup(string) - case .rawBytes(let rawBytes): - return strdup(rawBytes) - } - } - - var stringValue: String? { - switch self { - case .string(let string): - return string - case .rawBytes(let rawBytes): - return String(validatingUTF8: rawBytes) - } - } - - var description: String { - switch self { - case .string(let string): - return string - case .rawBytes(let bytes): - return bytes.description - } - } - - var count: Int { - switch self { - case .string(let string): - return string.count - case .rawBytes(let rawBytes): - return strnlen(rawBytes, Int.max) - } - } - - func hash(into hasher: inout Hasher) { - // If Raw bytes is valid UTF8, hash it as so - switch self { - case .string(let string): - hasher.combine(string) - case .rawBytes(let bytes): - if let stringValue = self.stringValue { - hasher.combine(stringValue) - } else { - hasher.combine(bytes) - } - } - } - } -} - -extension FilePath { - static var currentWorkingDirectory: Self { - let path = getcwd(nil, 0)! - defer { free(path) } - return .init(String(cString: path)) - } -} - -extension Optional where Wrapped : Collection { - func withOptionalUnsafeBufferPointer(_ body: ((UnsafeBufferPointer)?) throws -> R) rethrows -> R { - switch self { - case .some(let wrapped): - guard let array: Array = wrapped as? Array else { - return try body(nil) - } - return try array.withUnsafeBufferPointer { ptr in - return try body(ptr) - } - case .none: - return try body(nil) - } - } -} - -extension Optional where Wrapped == String { - func withOptionalCString(_ body: ((UnsafePointer)?) throws -> R) rethrows -> R { - switch self { - case .none: - return try body(nil) - case .some(let wrapped): - return try wrapped.withCString { - return try body($0) - } - } - } - - var stringValue: String { - return self ?? "nil" - } -} - -// MARK: - Stubs for the one from Foundation -public enum QualityOfService: Int, Sendable { - case userInteractive = 0x21 - case userInitiated = 0x19 - case utility = 0x11 - case background = 0x09 - case `default` = -1 -} - -internal func withAsyncTaskCancellationHandler( - _ body: sending @escaping () async throws -> R, - onCancel handler: sending @escaping () async -> Void -) async rethrows -> R { - return try await withThrowingTaskGroup( - of: R?.self, - returning: R.self - ) { group in - group.addTask { - return try await body() - } - group.addTask { - // wait until cancelled - do { while true { try await Task.sleep(nanoseconds: 1_000_000_000) } } catch {} - // Run task cancel handler - await handler() - return nil - } - - while let result = try await group.next() { - if let result = result { - // As soon as the body finishes, cancel the group - group.cancelAll() - return result - } - } - fatalError("Unreachable") - } -} - diff --git a/Sources/_Subprocess/Subprocess+IO.swift b/Sources/_Subprocess/Subprocess+IO.swift deleted file mode 100644 index 1b43e421..00000000 --- a/Sources/_Subprocess/Subprocess+IO.swift +++ /dev/null @@ -1,437 +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 canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -import Dispatch -import SystemPackage - -// Naive Mutex so we don't have to update the macOS dependency -final class _Mutex: Sendable { - let lock = Lock() - - var value: Value - - init(_ value: Value) { - self.value = value - } - - func withLock(_ body: (inout Value) throws -> R) rethrows -> R { - self.lock.lock() - defer { self.lock.unlock() } - return try body(&self.value) - } -} - -// MARK: - Input -extension Subprocess { - /// `InputMethod` defines how should the standard input - /// of the subprocess receive inputs. - public struct InputMethod: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case noInput - case fileDescriptor(FileDescriptor, Bool) - } - - internal let method: Storage - - internal init(method: Storage) { - self.method = method - } - - internal func createExecutionInput() throws -> ExecutionInput { - switch self.method { - case .noInput: - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) - return .init(storage: .noInput(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - } - } - - /// Subprocess should read no input. This option is equivalent - /// to bind the stanard input to `/dev/null`. - public static var noInput: Self { - return .init(method: .noInput) - } - - /// Subprocess should read input from a given file descriptor. - /// - Parameters: - /// - fd: the file descriptor to read from - /// - closeAfterSpawningProcess: whether the file descriptor - /// should be automatically closed after subprocess is spawned. - public static func readFrom( - _ fd: FileDescriptor, - closeAfterSpawningProcess: Bool - ) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterSpawningProcess)) - } - } -} - -extension Subprocess { - /// `CollectedOutputMethod` defines how should Subprocess collect - /// output from child process' standard output and standard error - public struct CollectedOutputMethod: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case discarded - case fileDescriptor(FileDescriptor, Bool) - case collected(Int) - } - - internal let method: Storage - - internal init(method: Storage) { - self.method = method - } - - /// Subprocess shold dicard the child process output. - /// This option is equivalent to binding the child process - /// output to `/dev/null`. - public static var discard: Self { - return .init(method: .discarded) - } - /// Subprocess should write the child process output - /// to the file descriptor specified. - /// - Parameters: - /// - fd: the file descriptor to write to - /// - closeAfterSpawningProcess: whether to close the - /// file descriptor once the process is spawned. - public static func writeTo(_ fd: FileDescriptor, closeAfterSpawningProcess: Bool) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterSpawningProcess)) - } - /// Subprocess should collect the child process output - /// as `Data` with the given limit in bytes. The default - /// limit is 128kb. - public static func collect(upTo limit: Int = 128 * 1024) -> Self { - return .init(method: .collected(limit)) - } - - internal func createExecutionOutput() throws -> ExecutionOutput { - switch self.method { - case .discarded: - // Bind to /dev/null - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - return .init(storage: .discarded(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - case .collected(let limit): - let (readFd, writeFd) = try FileDescriptor.pipe() - return .init(storage: .collected(limit, readFd, writeFd)) - } - } - } - - /// `CollectedOutputMethod` defines how should Subprocess redirect - /// output from child process' standard output and standard error. - public struct RedirectedOutputMethod: Sendable, Hashable { - typealias Storage = CollectedOutputMethod.Storage - - internal let method: Storage - - internal init(method: Storage) { - self.method = method - } - - /// Subprocess shold dicard the child process output. - /// This option is equivalent to binding the child process - /// output to `/dev/null`. - public static var discard: Self { - return .init(method: .discarded) - } - /// Subprocess should redirect the child process output - /// to `Subprocess.standardOutput` or `Subprocess.standardError` - /// so they can be consumed as an AsyncSequence - public static var redirectToSequence: Self { - return .init(method: .collected(128 * 1024)) - } - /// Subprocess shold write the child process output - /// to the file descriptor specified. - /// - Parameters: - /// - fd: the file descriptor to write to - /// - closeAfterSpawningProcess: whether to close the - /// file descriptor once the process is spawned. - public static func writeTo( - _ fd: FileDescriptor, - closeAfterSpawningProcess: Bool - ) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterSpawningProcess)) - } - - internal func createExecutionOutput() throws -> ExecutionOutput { - switch self.method { - case .discarded: - // Bind to /dev/null - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - return .init(storage: .discarded(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - case .collected(let limit): - let (readFd, writeFd) = try FileDescriptor.pipe() - return .init(storage: .collected(limit, readFd, writeFd)) - } - } - } -} - -// MARK: - Execution IO -extension Subprocess { - internal final class ExecutionInput: Sendable, Hashable { - - - internal enum Storage: Sendable, Hashable { - case noInput(FileDescriptor?) - case customWrite(FileDescriptor?, FileDescriptor?) - case fileDescriptor(FileDescriptor?, Bool) - } - - let storage: _Mutex - - internal init(storage: Storage) { - self.storage = .init(storage) - } - - internal func getReadFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .noInput(let readFd): - return readFd - case .customWrite(let readFd, _): - return readFd - case .fileDescriptor(let readFd, _): - return readFd - } - } - } - - internal func getWriteFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .noInput(_), .fileDescriptor(_, _): - return nil - case .customWrite(_, let writeFd): - return writeFd - } - } - } - - internal func closeChildSide() throws { - try self.storage.withLock { - switch $0 { - case .noInput(let devnull): - try devnull?.close() - $0 = .noInput(nil) - case .customWrite(let readFd, let writeFd): - try readFd?.close() - $0 = .customWrite(nil, writeFd) - case .fileDescriptor(let fd, let closeWhenDone): - // User passed in fd - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - } - } - } - - internal func closeParentSide() throws { - try self.storage.withLock { - switch $0 { - case .noInput(_), .fileDescriptor(_, _): - break - case .customWrite(let readFd, let writeFd): - // The parent fd should have been closed - // in the `body` when writer.finish() is called - // But in case it isn't call it agian - try writeFd?.close() - $0 = .customWrite(readFd, nil) - } - } - } - - internal func closeAll() throws { - try self.storage.withLock { - switch $0 { - case .noInput(let readFd): - try readFd?.close() - $0 = .noInput(nil) - case .customWrite(let readFd, let writeFd): - var readFdCloseError: Error? - var writeFdCloseError: Error? - do { - try readFd?.close() - } catch { - readFdCloseError = error - } - do { - try writeFd?.close() - } catch { - writeFdCloseError = error - } - $0 = .customWrite(nil, nil) - if let readFdCloseError { - throw readFdCloseError - } - if let writeFdCloseError { - throw writeFdCloseError - } - case .fileDescriptor(let fd, let closeWhenDone): - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - } - } - } - - public func hash(into hasher: inout Hasher) { - self.storage.withLock { - hasher.combine($0) - } - } - - public static func == ( - lhs: Subprocess.ExecutionInput, - rhs: Subprocess.ExecutionInput - ) -> Bool { - return lhs.storage.withLock { lhsStorage in - rhs.storage.withLock { rhsStorage in - return lhsStorage == rhsStorage - } - } - } - } - - internal final class ExecutionOutput: Sendable { - internal enum Storage: Sendable { - case discarded(FileDescriptor?) - case fileDescriptor(FileDescriptor?, Bool) - case collected(Int, FileDescriptor?, FileDescriptor?) - } - - private let storage: _Mutex - - internal init(storage: Storage) { - self.storage = .init(storage) - } - - internal func getWriteFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - return writeFd - case .fileDescriptor(let writeFd, _): - return writeFd - case .collected(_, _, let writeFd): - return writeFd - } - } - } - - internal func getReadFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - return nil - case .collected(_, let readFd, _): - return readFd - } - } - } - - internal func consumeCollectedFileDescriptor() -> (limit: Int, fd: FileDescriptor?)? { - return self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - // The output has been written somewhere else - return nil - case .collected(let limit, let readFd, let writeFd): - $0 = .collected(limit, nil, writeFd) - return (limit, readFd) - } - } - } - - internal func closeChildSide() throws { - try self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - try writeFd?.close() - $0 = .discarded(nil) - case .fileDescriptor(let fd, let closeWhenDone): - // User passed fd - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - case .collected(let limit, let readFd, let writeFd): - try writeFd?.close() - $0 = .collected(limit, readFd, nil) - } - } - } - - internal func closeParentSide() throws { - try self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - break - case .collected(let limit, let readFd, let writeFd): - try readFd?.close() - $0 = .collected(limit, nil, writeFd) - } - } - } - - internal func closeAll() throws { - try self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - try writeFd?.close() - $0 = .discarded(nil) - case .fileDescriptor(let fd, let closeWhenDone): - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - case .collected(let limit, let readFd, let writeFd): - var readFdCloseError: Error? - var writeFdCloseError: Error? - do { - try readFd?.close() - } catch { - readFdCloseError = error - } - do { - try writeFd?.close() - } catch { - writeFdCloseError = error - } - $0 = .collected(limit, nil, nil) - if let readFdCloseError { - throw readFdCloseError - } - if let writeFdCloseError { - throw writeFdCloseError - } - } - } - } - } -} - diff --git a/Sources/_Subprocess/Subprocess+Teardown.swift b/Sources/_Subprocess/Subprocess+Teardown.swift deleted file mode 100644 index 8b9af32d..00000000 --- a/Sources/_Subprocess/Subprocess+Teardown.swift +++ /dev/null @@ -1,125 +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 canImport(Darwin) || canImport(Glibc) - -#if canImport(Darwin) -import Darwin -#elseif canImport(Glibc) -import Glibc -#endif - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - /// A step in the graceful shutdown teardown sequence. - /// It consists of a signal to send to the child process and the - /// number of nanoseconds allowed for the child process to exit - /// before proceeding to the next step. - public struct TeardownStep: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case sendSignal(Signal, allowedNanoseconds: UInt64) - case kill - } - var storage: Storage - - /// Sends `signal` to the process and provides `allowedNanoSecondsToExit` - /// nanoseconds for the process to exit before proceeding to the next step. - /// The final step in the sequence will always send a `.kill` signal. - public static func sendSignal( - _ signal: Signal, - allowedNanoSecondsToExit: UInt64 - ) -> Self { - return Self( - storage: .sendSignal( - signal, - allowedNanoseconds: allowedNanoSecondsToExit - ) - ) - } - } -} - -extension Subprocess { - internal func runTeardownSequence(_ sequence: [TeardownStep]) async { - // First insert the `.kill` step - let finalSequence = sequence + [TeardownStep(storage: .kill)] - for step in finalSequence { - enum TeardownStepCompletion { - case processHasExited - case processStillAlive - case killedTheProcess - } - let stepCompletion: TeardownStepCompletion - - guard self.isAlive() else { - return - } - - switch step.storage { - case .sendSignal(let signal, let allowedNanoseconds): - stepCompletion = await withTaskGroup(of: TeardownStepCompletion.self) { group in - group.addTask { - do { - try await Task.sleep(nanoseconds: allowedNanoseconds) - return .processStillAlive - } catch { - // teardown(using:) cancells this task - // when process has exited - return .processHasExited - } - } - try? self.send(signal, toProcessGroup: false) - return await group.next()! - } - case .kill: - try? self.send(.kill, toProcessGroup: false) - stepCompletion = .killedTheProcess - } - - switch stepCompletion { - case .killedTheProcess, .processHasExited: - return - case .processStillAlive: - // Continue to next step - break - } - } - } -} - -extension Subprocess { - private func isAlive() -> Bool { - return kill(self.processIdentifier.value, 0) == 0 - } -} - -func withUncancelledTask( - returning: R.Type = R.self, - _ body: @Sendable @escaping () async -> R -) async -> R { - // This looks unstructured but it isn't, please note that we `await` `.value` of this task. - // The reason we need this separate `Task` is that in general, we cannot assume that code performs to our - // expectations if the task we run it on is already cancelled. However, in some cases we need the code to - // run regardless -- even if our task is already cancelled. Therefore, we create a new, uncancelled task here. - await Task { - await body() - }.value -} - -#endif // canImport(Darwin) || canImport(Glibc) diff --git a/Sources/_Subprocess/Subprocess.swift b/Sources/_Subprocess/Subprocess.swift deleted file mode 100644 index 35f4a2c2..00000000 --- a/Sources/_Subprocess/Subprocess.swift +++ /dev/null @@ -1,315 +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 SystemPackage - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -/// An object that represents a subprocess of the current process. -/// -/// Using `Subprocess`, your program can run another program as a subprocess -/// and can monitor that program’s execution. A `Subprocess` object creates a -/// **separate executable** entity; it’s different from `Thread` because it doesn’t -/// share memory space with the process that creates it. -public struct Subprocess: Sendable { - /// The process identifier of the current subprocess - public let processIdentifier: ProcessIdentifier - - internal let executionInput: ExecutionInput - internal let executionOutput: ExecutionOutput - internal let executionError: ExecutionOutput -#if os(Windows) - internal let consoleBehavior: PlatformOptions.ConsoleBehavior -#endif - - /// The standard output of the subprocess. - /// Accessing this property will **fatalError** if - /// - `.output` wasn't set to `.redirectToSequence` when the subprocess was spawned; - /// - This property was accessed multiple times. Subprocess communicates with - /// parent process via pipe under the hood and each pipe can only be consumed ones. - public var standardOutput: some _AsyncSequence { - guard let (_, fd) = self.executionOutput - .consumeCollectedFileDescriptor() else { - fatalError("The standard output was not redirected") - } - guard let fd = fd else { - fatalError("The standard output has already been closed") - } - return AsyncDataSequence(fileDescriptor: fd) - } - - /// The standard error of the subprocess. - /// Accessing this property will **fatalError** if - /// - `.error` wasn't set to `.redirectToSequence` when the subprocess was spawned; - /// - This property was accessed multiple times. Subprocess communicates with - /// parent process via pipe under the hood and each pipe can only be consumed ones. - public var standardError: some _AsyncSequence { - guard let (_, fd) = self.executionError - .consumeCollectedFileDescriptor() else { - fatalError("The standard error was not redirected") - } - guard let fd = fd else { - fatalError("The standard error has already been closed") - } - return AsyncDataSequence(fileDescriptor: fd) - } -} - -// MARK: - Teardown -#if canImport(Darwin) || canImport(Glibc) -extension Subprocess { - /// Performs a sequence of teardown steps on the Subprocess. - /// Teardown sequence always ends with a `.kill` signal - /// - Parameter sequence: The steps to perform. - public func teardown(using sequence: [TeardownStep]) async { - await withUncancelledTask { - await self.runTeardownSequence(sequence) - } - } -} -#endif - -// MARK: - StandardInputWriter -extension Subprocess { - /// A writer that writes to the standard input of the subprocess. - public struct StandardInputWriter { - - private let input: ExecutionInput - - init(input: ExecutionInput) { - self.input = input - } - - /// Write a sequence of UInt8 to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ sequence: S) async throws where S : Sequence, S.Element == UInt8 { - guard let fd: FileDescriptor = self.input.getWriteFileDescriptor() else { - fatalError("Attempting to write to a file descriptor that's already closed") - } - try await fd.write(sequence) - } - - /// Write a sequence of CChar to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ sequence: S) async throws where S : Sequence, S.Element == CChar { - try await self.write(sequence.map { UInt8($0) }) - } - - /// Write a AsyncSequence of CChar to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ asyncSequence: S) async throws where S.Element == CChar { - let sequence = try await Array(asyncSequence).map { UInt8($0) } - try await self.write(sequence) - } - - /// Write a AsyncSequence of UInt8 to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ asyncSequence: S) async throws where S.Element == UInt8 { - let sequence = try await Array(asyncSequence) - try await self.write(sequence) - } - - /// Signal all writes are finished - public func finish() async throws { - try self.input.closeParentSide() - } - } -} - -@available(macOS, unavailable) -@available(iOS, unavailable) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -@available(*, unavailable) -extension Subprocess.StandardInputWriter : Sendable {} - -// MARK: - Result -extension Subprocess { - /// A simple wrapper around the generic result returned by the - /// `run` closures with the corresponding `TerminationStatus` - /// of the child process. - public struct ExecutionResult: Sendable { - /// The termination status of the child process - public let terminationStatus: TerminationStatus - /// The result returned by the closure passed to `.run` methods - public let value: T - - internal init(terminationStatus: TerminationStatus, value: T) { - self.terminationStatus = terminationStatus - self.value = value - } - } - - /// The result of a subprocess execution with its collected - /// standard output and standard error. - public struct CollectedResult: Sendable, Hashable, Codable { - /// The process identifier for the executed subprocess - public let processIdentifier: ProcessIdentifier - /// The termination status of the executed subprocess - public let terminationStatus: TerminationStatus - private let _standardOutput: Data? - private let _standardError: Data? - - /// The collected standard output value for the subprocess. - /// Accessing this property will *fatalError* if the - /// corresponding `CollectedOutputMethod` is not set to - /// `.collect` or `.collect(upTo:)` - public var standardOutput: Data { - guard let output = self._standardOutput else { - fatalError("standardOutput is only available if the Subprocess was ran with .collect as output") - } - return output - } - /// The collected standard error value for the subprocess. - /// Accessing this property will *fatalError* if the - /// corresponding `CollectedOutputMethod` is not set to - /// `.collect` or `.collect(upTo:)` - public var standardError: Data { - guard let output = self._standardError else { - fatalError("standardError is only available if the Subprocess was ran with .collect as error ") - } - return output - } - - internal init( - processIdentifier: ProcessIdentifier, - terminationStatus: TerminationStatus, - standardOutput: Data?, - standardError: Data?) { - self.processIdentifier = processIdentifier - self.terminationStatus = terminationStatus - self._standardOutput = standardOutput - self._standardError = standardError - } - } -} - -extension Subprocess.ExecutionResult: Equatable where T : Equatable {} - -extension Subprocess.ExecutionResult: Hashable where T : Hashable {} - -extension Subprocess.ExecutionResult: Codable where T : Codable {} - -extension Subprocess.ExecutionResult: CustomStringConvertible where T : CustomStringConvertible { - public var description: String { - return """ -Subprocess.ExecutionResult( - terminationStatus: \(self.terminationStatus.description), - value: \(self.value.description) -) -""" - } -} - -extension Subprocess.ExecutionResult: CustomDebugStringConvertible where T : CustomDebugStringConvertible { - public var debugDescription: String { - return """ -Subprocess.ExecutionResult( - terminationStatus: \(self.terminationStatus.debugDescription), - value: \(self.value.debugDescription) -) -""" - } -} - -extension Subprocess.CollectedResult : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - return """ -Subprocess.CollectedResult( - processIdentifier: \(self.processIdentifier.description), - terminationStatus: \(self.terminationStatus.description), - standardOutput: \(self._standardOutput?.description ?? "not captured"), - standardError: \(self._standardError?.description ?? "not captured") -) -""" - } - - public var debugDescription: String { - return """ -Subprocess.CollectedResult( - processIdentifier: \(self.processIdentifier.debugDescription), - terminationStatus: \(self.terminationStatus.debugDescription), - standardOutput: \(self._standardOutput?.debugDescription ?? "not captured"), - standardError: \(self._standardError?.debugDescription ?? "not captured") -) -""" - } -} - -// MARK: - Internal -extension Subprocess { - internal enum OutputCapturingState { - case standardOutputCaptured(Data?) - case standardErrorCaptured(Data?) - } - - internal typealias CapturedIOs = (standardOutput: Data?, standardError: Data?) - - private func capture(fileDescriptor: FileDescriptor, maxLength: Int) async throws -> Data { - return try await fileDescriptor.readUntilEOF(upToLength: maxLength) - } - - internal func captureStandardOutput() async throws -> Data? { - guard let (limit, readFd) = self.executionOutput - .consumeCollectedFileDescriptor(), - let readFd = readFd else { - return nil - } - defer { - try? readFd.close() - } - return try await self.capture(fileDescriptor: readFd, maxLength: limit) - } - - internal func captureStandardError() async throws -> Data? { - guard let (limit, readFd) = self.executionError - .consumeCollectedFileDescriptor(), - let readFd = readFd else { - return nil - } - defer { - try? readFd.close() - } - return try await self.capture(fileDescriptor: readFd, maxLength: limit) - } - - internal func captureIOs() async throws -> CapturedIOs { - return try await withThrowingTaskGroup(of: OutputCapturingState.self) { group in - group.addTask { - let stdout = try await self.captureStandardOutput() - return .standardOutputCaptured(stdout) - } - group.addTask { - let stderr = try await self.captureStandardError() - return .standardErrorCaptured(stderr) - } - - var stdout: Data? - var stderror: Data? - while let state = try await group.next() { - switch state { - case .standardOutputCaptured(let output): - stdout = output - case .standardErrorCaptured(let error): - stderror = error - } - } - return (standardOutput: stdout, standardError: stderror) - } - } -} diff --git a/Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift b/Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift new file mode 100644 index 00000000..3ec42173 --- /dev/null +++ b/Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift @@ -0,0 +1,179 @@ +//===----------------------------------------------------------------------===// +// +// 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 SubprocessFoundation + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif // canImport(Darwin) + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +internal import Dispatch + +/// A concrete `Input` type for subprocesses that reads input +/// from a given `Data`. +public struct DataInput: InputProtocol { + private let data: Data + + public func write(with writer: StandardInputWriter) async throws { + _ = try await writer.write(self.data) + } + + internal init(data: Data) { + self.data = data + } +} + +/// A concrete `Input` type for subprocesses that accepts input +/// from a specified sequence of `Data`. +public struct DataSequenceInput< + InputSequence: Sequence & Sendable +>: InputProtocol where InputSequence.Element == Data { + private let sequence: InputSequence + + public func write(with writer: StandardInputWriter) async throws { + var buffer = Data() + for chunk in self.sequence { + buffer.append(chunk) + } + _ = try await writer.write(buffer) + } + + internal init(underlying: InputSequence) { + self.sequence = underlying + } +} + +/// A concrete `Input` type for subprocesses that reads input +/// from a given async sequence of `Data`. +public struct DataAsyncSequenceInput< + InputSequence: AsyncSequence & Sendable +>: InputProtocol where InputSequence.Element == Data { + private let sequence: InputSequence + + private func writeChunk(_ chunk: Data, with writer: StandardInputWriter) async throws { + _ = try await writer.write(chunk) + } + + public func write(with writer: StandardInputWriter) async throws { + for try await chunk in self.sequence { + try await self.writeChunk(chunk, with: writer) + } + } + + internal init(underlying: InputSequence) { + self.sequence = underlying + } +} + +extension InputProtocol { + /// Create a Subprocess input from a `Data` + public static func data(_ data: Data) -> Self where Self == DataInput { + return DataInput(data: data) + } + + /// Create a Subprocess input from a `Sequence` of `Data`. + public static func sequence( + _ sequence: InputSequence + ) -> Self where Self == DataSequenceInput { + return .init(underlying: sequence) + } + + /// Create a Subprocess input from a `AsyncSequence` of `Data`. + public static func sequence( + _ asyncSequence: InputSequence + ) -> Self where Self == DataAsyncSequenceInput { + return .init(underlying: asyncSequence) + } +} + +extension StandardInputWriter { + /// Write a `Data` to the standard input of the subprocess. + /// - Parameter data: The sequence of bytes to write. + /// - Returns number of bytes written. + public func write( + _ data: Data + ) async throws -> Int { + return try await self.fileDescriptor.wrapped.write(data) + } + + /// Write a AsyncSequence of Data to the standard input of the subprocess. + /// - Parameter sequence: The sequence of bytes to write. + /// - Returns number of bytes written. + public func write( + _ asyncSequence: AsyncSendableSequence + ) async throws -> Int where AsyncSendableSequence.Element == Data { + var buffer = Data() + for try await data in asyncSequence { + buffer.append(data) + } + return try await self.write(buffer) + } +} + +extension FileDescriptor { + #if os(Windows) + internal func write( + _ data: Data + ) async throws -> Int { + try await withCheckedThrowingContinuation { continuation in + // TODO: Figure out a better way to asynchornously write + DispatchQueue.global(qos: .userInitiated).async { + data.withUnsafeBytes { + self.write($0) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) + } + } + } + } + } + } + #else + internal func write( + _ data: Data + ) async throws -> Int { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + let dispatchData = data.withUnsafeBytes { + return DispatchData( + bytesNoCopy: $0, + deallocator: .custom( + nil, + { + // noop + } + ) + ) + } + self.write(dispatchData) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) + } + } + } + } + #endif +} + +#endif // SubprocessFoundation diff --git a/Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift b/Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift new file mode 100644 index 00000000..fac0bb8f --- /dev/null +++ b/Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// 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 SubprocessFoundation + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif + +/// A concrete `Output` type for subprocesses that collects output +/// from the subprocess as `Data`. This option must be used with +/// the `run()` method that returns a `CollectedResult` +public struct DataOutput: OutputProtocol { + public typealias OutputType = Data + public let maxSize: Int + + public func output(from buffer: some Sequence) throws -> Data { + return Data(buffer) + } + + internal init(limit: Int) { + self.maxSize = limit + } +} + +extension OutputProtocol where Self == DataOutput { + /// Create a `Subprocess` output that collects output as `Data` + /// up to 128kb. + public static var data: Self { + return .data(limit: 128 * 1024) + } + + /// Create a `Subprocess` output that collects output as `Data` + /// with given max number of bytes to collect. + public static func data(limit: Int) -> Self { + return .init(limit: limit) + } +} + + +#endif // SubprocessFoundation diff --git a/Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift b/Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift new file mode 100644 index 00000000..10697091 --- /dev/null +++ b/Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// 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 SubprocessFoundation && SubprocessSpan + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif // canImport(Darwin) + +internal import Dispatch + + +extension Data { + init(_ s: borrowing RawSpan) { + self = s.withUnsafeBytes { Data($0) } + } + + public var bytes: RawSpan { + // FIXME: For demo purpose only + let ptr = self.withUnsafeBytes { ptr in + return ptr + } + let span = RawSpan(_unsafeBytes: ptr) + return _overrideLifetime(of: span, to: self) + } +} + + +extension DataProtocol { + var bytes: RawSpan { + _read { + if self.regions.isEmpty { + let empty = UnsafeRawBufferPointer(start: nil, count: 0) + let span = RawSpan(_unsafeBytes: empty) + yield _overrideLifetime(of: span, to: self) + } else if self.regions.count == 1 { + // Easy case: there is only one region in the data + let ptr = self.regions.first!.withUnsafeBytes { ptr in + return ptr + } + let span = RawSpan(_unsafeBytes: ptr) + yield _overrideLifetime(of: span, to: self) + } else { + // This data contains discontiguous chunks. We have to + // copy and make a contiguous chunk + var contiguous: ContiguousArray? + for region in self.regions { + if contiguous != nil { + contiguous?.append(contentsOf: region) + } else { + contiguous = .init(region) + } + } + let ptr = contiguous!.withUnsafeBytes { ptr in + return ptr + } + let span = RawSpan(_unsafeBytes: ptr) + yield _overrideLifetime(of: span, to: self) + } + } + } +} + +#endif // SubprocessFoundation diff --git a/Sources/_Subprocess/Teardown.swift b/Sources/_Subprocess/Teardown.swift new file mode 100644 index 00000000..b2da85a5 --- /dev/null +++ b/Sources/_Subprocess/Teardown.swift @@ -0,0 +1,217 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import _SubprocessCShims + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +/// A step in the graceful shutdown teardown sequence. +/// It consists of an action to perform on the child process and the +/// duration allowed for the child process to exit before proceeding +/// to the next step. +public struct TeardownStep: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + #if !os(Windows) + case sendSignal(Signal, allowedDuration: Duration) + #endif + case gracefulShutDown(allowedDuration: Duration) + case kill + } + var storage: Storage + + #if !os(Windows) + /// Sends `signal` to the process and allows `allowedDurationToExit` + /// for the process to exit before proceeding to the next step. + /// The final step in the sequence will always send a `.kill` signal. + public static func send( + signal: Signal, + allowedDurationToNextStep: Duration + ) -> Self { + return Self( + storage: .sendSignal( + signal, + allowedDuration: allowedDurationToNextStep + ) + ) + } + #endif // !os(Windows) + + /// Attempt to perform a graceful shutdown and allows + /// `allowedDurationToNextStep` for the process to exit + /// before proceeding to the next step: + /// - On Unix: send `SIGTERM` + /// - On Windows: + /// 1. Attempt to send `VM_CLOSE` if the child process is a GUI process; + /// 2. Attempt to send `CTRL_C_EVENT` to console; + /// 3. Attempt to send `CTRL_BREAK_EVENT` to process group. + public static func gracefulShutDown( + allowedDurationToNextStep: Duration + ) -> Self { + return Self( + storage: .gracefulShutDown( + allowedDuration: allowedDurationToNextStep + ) + ) + } +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { + /// Performs a sequence of teardown steps on the Subprocess. + /// Teardown sequence always ends with a `.kill` signal + /// - Parameter sequence: The steps to perform. + public func teardown(using sequence: some Sequence & Sendable) async { + await withUncancelledTask { + await self.runTeardownSequence(sequence) + } + } +} + +internal enum TeardownStepCompletion { + case processHasExited + case processStillAlive + case killedTheProcess +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { + internal func gracefulShutDown( + allowedDurationToNextStep duration: Duration + ) async { + #if os(Windows) + guard + let processHandle = OpenProcess( + DWORD(PROCESS_QUERY_INFORMATION | SYNCHRONIZE), + false, + self.processIdentifier.value + ) + else { + // Nothing more we can do + return + } + defer { + CloseHandle(processHandle) + } + + // 1. Attempt to send WM_CLOSE to the main window + if _subprocess_windows_send_vm_close( + self.processIdentifier.value + ) { + try? await Task.sleep(for: duration) + } + + // 2. Attempt to attach to the console and send CTRL_C_EVENT + if AttachConsole(self.processIdentifier.value) { + // Disable Ctrl-C handling in this process + if SetConsoleCtrlHandler(nil, true) { + if GenerateConsoleCtrlEvent(DWORD(CTRL_C_EVENT), 0) { + // We successfully sent the event. wait for the process to exit + try? await Task.sleep(for: duration) + } + // Re-enable Ctrl-C handling + SetConsoleCtrlHandler(nil, false) + } + // Detach console + FreeConsole() + } + + // 3. Attempt to send CTRL_BREAK_EVENT to the process group + if GenerateConsoleCtrlEvent(DWORD(CTRL_BREAK_EVENT), self.processIdentifier.value) { + // Wait for process to exit + try? await Task.sleep(for: duration) + } + #else + // Send SIGTERM + try? self.send(signal: .terminate) + #endif + } + + internal func runTeardownSequence(_ sequence: some Sequence & Sendable) async { + // First insert the `.kill` step + let finalSequence = sequence + [TeardownStep(storage: .kill)] + for step in finalSequence { + let stepCompletion: TeardownStepCompletion + + switch step.storage { + case .gracefulShutDown(let allowedDuration): + stepCompletion = await withTaskGroup(of: TeardownStepCompletion.self) { group in + group.addTask { + do { + try await Task.sleep(for: allowedDuration) + return .processStillAlive + } catch { + // teardown(using:) cancells this task + // when process has exited + return .processHasExited + } + } + await self.gracefulShutDown(allowedDurationToNextStep: allowedDuration) + return await group.next()! + } + #if !os(Windows) + case .sendSignal(let signal, let allowedDuration): + stepCompletion = await withTaskGroup(of: TeardownStepCompletion.self) { group in + group.addTask { + do { + try await Task.sleep(for: allowedDuration) + return .processStillAlive + } catch { + // teardown(using:) cancells this task + // when process has exited + return .processHasExited + } + } + try? self.send(signal: signal) + return await group.next()! + } + #endif // !os(Windows) + case .kill: + #if os(Windows) + try? self.terminate(withExitCode: 0) + #else + try? self.send(signal: .kill) + #endif + stepCompletion = .killedTheProcess + } + + switch stepCompletion { + case .killedTheProcess, .processHasExited: + return + case .processStillAlive: + // Continue to next step + break + } + } + } +} + +func withUncancelledTask( + returning: Result.Type = Result.self, + _ body: @Sendable @escaping () async -> Result +) async -> Result { + // This looks unstructured but it isn't, please note that we `await` `.value` of this task. + // The reason we need this separate `Task` is that in general, we cannot assume that code performs to our + // expectations if the task we run it on is already cancelled. However, in some cases we need the code to + // run regardless -- even if our task is already cancelled. Therefore, we create a new, uncancelled task here. + await Task { + await body() + }.value +} diff --git a/Sources/_Subprocess/_nio_locks.swift b/Sources/_Subprocess/_nio_locks.swift deleted file mode 100644 index 49053d04..00000000 --- a/Sources/_Subprocess/_nio_locks.swift +++ /dev/null @@ -1,526 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftNIO open source project -// -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftNIO project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(Darwin) -import Darwin -#elseif os(Windows) -import ucrt -import WinSDK -#elseif canImport(Glibc) -import Glibc -#elseif canImport(Musl) -import Musl -#else -#error("The concurrency lock module was unable to identify your C library.") -#endif - -/// A threading lock based on `libpthread` instead of `libdispatch`. -/// -/// This object provides a lock on top of a single `pthread_mutex_t`. This kind -/// of lock is safe to use with `libpthread`-based threading models, such as the -/// one used by NIO. On Windows, the lock is based on the substantially similar -/// `SRWLOCK` type. -@available(*, deprecated, renamed: "NIOLock") -public final class Lock { -#if os(Windows) - fileprivate let mutex: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#else - fileprivate let mutex: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#endif - - /// Create a new lock. - public init() { -#if os(Windows) - InitializeSRWLock(self.mutex) -#else - var attr = pthread_mutexattr_t() - pthread_mutexattr_init(&attr) - debugOnly { - pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) - } - - let err = pthread_mutex_init(self.mutex, &attr) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - deinit { -#if os(Windows) - // SRWLOCK does not need to be free'd -#else - let err = pthread_mutex_destroy(self.mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - mutex.deallocate() - } - - /// Acquire the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `unlock`, to simplify lock handling. - public func lock() { -#if os(Windows) - AcquireSRWLockExclusive(self.mutex) -#else - let err = pthread_mutex_lock(self.mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - /// Release the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `lock`, to simplify lock handling. - public func unlock() { -#if os(Windows) - ReleaseSRWLockExclusive(self.mutex) -#else - let err = pthread_mutex_unlock(self.mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - /// Acquire the lock for the duration of the given block. - /// - /// This convenience method should be preferred to `lock` and `unlock` in - /// most situations, as it ensures that the lock will be released regardless - /// of how `body` exits. - /// - /// - Parameter body: The block to execute while holding the lock. - /// - Returns: The value returned by the block. - @inlinable - public func withLock(_ body: () throws -> T) rethrows -> T { - self.lock() - defer { - self.unlock() - } - return try body() - } - - // specialise Void return (for performance) - @inlinable - public func withLockVoid(_ body: () throws -> Void) rethrows -> Void { - try self.withLock(body) - } -} - -/// A `Lock` with a built-in state variable. -/// -/// This class provides a convenience addition to `Lock`: it provides the ability to wait -/// until the state variable is set to a specific value to acquire the lock. -public final class ConditionLock { - private var _value: T - private let mutex: NIOLock -#if os(Windows) - private let cond: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#else - private let cond: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#endif - - /// Create the lock, and initialize the state variable to `value`. - /// - /// - Parameter value: The initial value to give the state variable. - public init(value: T) { - self._value = value - self.mutex = NIOLock() -#if os(Windows) - InitializeConditionVariable(self.cond) -#else - let err = pthread_cond_init(self.cond, nil) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - } - - deinit { -#if os(Windows) - // condition variables do not need to be explicitly destroyed -#else - let err = pthread_cond_destroy(self.cond) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - self.cond.deallocate() - } - - /// Acquire the lock, regardless of the value of the state variable. - public func lock() { - self.mutex.lock() - } - - /// Release the lock, regardless of the value of the state variable. - public func unlock() { - self.mutex.unlock() - } - - /// The value of the state variable. - /// - /// Obtaining the value of the state variable requires acquiring the lock. - /// This means that it is not safe to access this property while holding the - /// lock: it is only safe to use it when not holding it. - public var value: T { - self.lock() - defer { - self.unlock() - } - return self._value - } - - /// Acquire the lock when the state variable is equal to `wantedValue`. - /// - /// - Parameter wantedValue: The value to wait for the state variable - /// to have before acquiring the lock. - public func lock(whenValue wantedValue: T) { - self.lock() - while true { - if self._value == wantedValue { - break - } - self.mutex.withLockPrimitive { mutex in -#if os(Windows) - let result = SleepConditionVariableSRW(self.cond, mutex, INFINITE, 0) - precondition(result, "\(#function) failed in SleepConditionVariableSRW with error \(GetLastError())") -#else - let err = pthread_cond_wait(self.cond, mutex) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - } - } - } - - /// Acquire the lock when the state variable is equal to `wantedValue`, - /// waiting no more than `timeoutSeconds` seconds. - /// - /// - Parameter wantedValue: The value to wait for the state variable - /// to have before acquiring the lock. - /// - Parameter timeoutSeconds: The number of seconds to wait to acquire - /// the lock before giving up. - /// - Returns: `true` if the lock was acquired, `false` if the wait timed out. - public func lock(whenValue wantedValue: T, timeoutSeconds: Double) -> Bool { - precondition(timeoutSeconds >= 0) - -#if os(Windows) - var dwMilliseconds: DWORD = DWORD(timeoutSeconds * 1000) - - self.lock() - while true { - if self._value == wantedValue { - return true - } - - let dwWaitStart = timeGetTime() - if !SleepConditionVariableSRW(self.cond, self.mutex._storage.mutex, - dwMilliseconds, 0) { - let dwError = GetLastError() - if (dwError == ERROR_TIMEOUT) { - self.unlock() - return false - } - fatalError("SleepConditionVariableSRW: \(dwError)") - } - - // NOTE: this may be a spurious wakeup, adjust the timeout accordingly - dwMilliseconds = dwMilliseconds - (timeGetTime() - dwWaitStart) - } -#else - let nsecPerSec: Int64 = 1000000000 - self.lock() - /* the timeout as a (seconds, nano seconds) pair */ - let timeoutNS = Int64(timeoutSeconds * Double(nsecPerSec)) - - var curTime = timeval() - gettimeofday(&curTime, nil) - - let allNSecs: Int64 = timeoutNS + Int64(curTime.tv_usec) * 1000 - var timeoutAbs = timespec(tv_sec: curTime.tv_sec + Int((allNSecs / nsecPerSec)), - tv_nsec: Int(allNSecs % nsecPerSec)) - assert(timeoutAbs.tv_nsec >= 0 && timeoutAbs.tv_nsec < Int(nsecPerSec)) - assert(timeoutAbs.tv_sec >= curTime.tv_sec) - return self.mutex.withLockPrimitive { mutex -> Bool in - while true { - if self._value == wantedValue { - return true - } - switch pthread_cond_timedwait(self.cond, mutex, &timeoutAbs) { - case 0: - continue - case ETIMEDOUT: - self.unlock() - return false - case let e: - fatalError("caught error \(e) when calling pthread_cond_timedwait") - } - } - } -#endif - } - - /// Release the lock, setting the state variable to `newValue`. - /// - /// - Parameter newValue: The value to give to the state variable when we - /// release the lock. - public func unlock(withValue newValue: T) { - self._value = newValue - self.unlock() -#if os(Windows) - WakeAllConditionVariable(self.cond) -#else - let err = pthread_cond_broadcast(self.cond) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - } -} - -/// A utility function that runs the body code only in debug builds, without -/// emitting compiler warnings. -/// -/// This is currently the only way to do this in Swift: see -/// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion. -@inlinable -internal func debugOnly(_ body: () -> Void) { - assert({ body(); return true }()) -} - -@available(*, deprecated) -extension Lock: @unchecked Sendable {} -extension ConditionLock: @unchecked Sendable {} - -#if os(Windows) -@usableFromInline -typealias LockPrimitive = SRWLOCK -#else -@usableFromInline -typealias LockPrimitive = pthread_mutex_t -#endif - -@usableFromInline -enum LockOperations { } - -extension LockOperations { - @inlinable - static func create(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - InitializeSRWLock(mutex) -#else - var attr = pthread_mutexattr_t() - pthread_mutexattr_init(&attr) - debugOnly { - pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) - } - - let err = pthread_mutex_init(mutex, &attr) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - @inlinable - static func destroy(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - // SRWLOCK does not need to be free'd -#else - let err = pthread_mutex_destroy(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - @inlinable - static func lock(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - AcquireSRWLockExclusive(mutex) -#else - let err = pthread_mutex_lock(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - @inlinable - static func unlock(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - ReleaseSRWLockExclusive(mutex) -#else - let err = pthread_mutex_unlock(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } -} - -// Tail allocate both the mutex and a generic value using ManagedBuffer. -// Both the header pointer and the elements pointer are stable for -// the class's entire lifetime. -// -// However, for safety reasons, we elect to place the lock in the "elements" -// section of the buffer instead of the head. The reasoning here is subtle, -// so buckle in. -// -// _As a practical matter_, the implementation of ManagedBuffer ensures that -// the pointer to the header is stable across the lifetime of the class, and so -// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader` -// the value of the header pointer will be the same. This is because ManagedBuffer uses -// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure -// that it does not invoke any weird Swift accessors that might copy the value. -// -// _However_, the header is also available via the `.header` field on the ManagedBuffer. -// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends -// do not interact with Swift's exclusivity model. That is, the various `with` functions do not -// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because -// there's literally no other way to perform the access, but for `.header` it's entirely possible -// to accidentally recursively read it. -// -// Our implementation is free from these issues, so we don't _really_ need to worry about it. -// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive -// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry, -// and future maintainers will be happier that we were cautious. -// -// See also: https://github.com/apple/swift/pull/40000 -@usableFromInline -final class LockStorage: ManagedBuffer { - - @inlinable - static func create(value: Value) -> Self { - let buffer = Self.create(minimumCapacity: 1) { _ in - return value - } - let storage = unsafeDowncast(buffer, to: Self.self) - - storage.withUnsafeMutablePointers { _, lockPtr in - LockOperations.create(lockPtr) - } - - return storage - } - - @inlinable - func lock() { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.lock(lockPtr) - } - } - - @inlinable - func unlock() { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.unlock(lockPtr) - } - } - - @inlinable - deinit { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.destroy(lockPtr) - } - } - - @inlinable - func withLockPrimitive(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { - try self.withUnsafeMutablePointerToElements { lockPtr in - return try body(lockPtr) - } - } - - @inlinable - func withLockedValue(_ mutate: (inout Value) throws -> T) rethrows -> T { - try self.withUnsafeMutablePointers { valuePtr, lockPtr in - LockOperations.lock(lockPtr) - defer { LockOperations.unlock(lockPtr) } - return try mutate(&valuePtr.pointee) - } - } -} - -extension LockStorage: @unchecked Sendable { } - -/// A threading lock based on `libpthread` instead of `libdispatch`. -/// -/// - note: ``NIOLock`` has reference semantics. -/// -/// This object provides a lock on top of a single `pthread_mutex_t`. This kind -/// of lock is safe to use with `libpthread`-based threading models, such as the -/// one used by NIO. On Windows, the lock is based on the substantially similar -/// `SRWLOCK` type. -public struct NIOLock { - @usableFromInline - internal let _storage: LockStorage - - /// Create a new lock. - @inlinable - public init() { - self._storage = .create(value: ()) - } - - /// Acquire the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `unlock`, to simplify lock handling. - @inlinable - public func lock() { - self._storage.lock() - } - - /// Release the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `lock`, to simplify lock handling. - @inlinable - public func unlock() { - self._storage.unlock() - } - - @inlinable - internal func withLockPrimitive(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { - return try self._storage.withLockPrimitive(body) - } -} - -extension NIOLock { - /// Acquire the lock for the duration of the given block. - /// - /// This convenience method should be preferred to `lock` and `unlock` in - /// most situations, as it ensures that the lock will be released regardless - /// of how `body` exits. - /// - /// - Parameter body: The block to execute while holding the lock. - /// - Returns: The value returned by the block. - @inlinable - public func withLock(_ body: () throws -> T) rethrows -> T { - self.lock() - defer { - self.unlock() - } - return try body() - } - - @inlinable - public func withLockVoid(_ body: () throws -> Void) rethrows -> Void { - try self.withLock(body) - } -} - -extension NIOLock: Sendable {} - -extension UnsafeMutablePointer { - @inlinable - func assertValidAlignment() { - assert(UInt(bitPattern: self) % UInt(MemoryLayout.alignment) == 0) - } -} diff --git a/Sources/_CShims/include/process_shims.h b/Sources/_SubprocessCShims/include/process_shims.h similarity index 74% rename from Sources/_CShims/include/process_shims.h rename to Sources/_SubprocessCShims/include/process_shims.h index 563b517f..35cbd2fe 100644 --- a/Sources/_CShims/include/process_shims.h +++ b/Sources/_SubprocessCShims/include/process_shims.h @@ -2,20 +2,17 @@ // // 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 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// #ifndef process_shims_h #define process_shims_h -#include "_CShimsTargetConditionals.h" +#include "target_conditionals.h" #if !TARGET_OS_WINDOWS #include @@ -24,6 +21,10 @@ #include #endif +#if __has_include() +vm_size_t _subprocess_vm_size(void); +#endif + #if TARGET_OS_MAC int _subprocess_spawn( pid_t * _Nonnull pid, @@ -60,6 +61,10 @@ int _was_process_signaled(int status); int _get_signal_code(int status); int _was_process_suspended(int status); +void _subprocess_lock_environ(void); +void _subprocess_unlock_environ(void); +char * _Nullable * _Nullable _subprocess_get_environ(void); + #if TARGET_OS_LINUX int _shims_snprintf( char * _Nonnull str, @@ -72,4 +77,15 @@ int _shims_snprintf( #endif // !TARGET_OS_WINDOWS +#if TARGET_OS_WINDOWS + +#ifndef _WINDEF_ +typedef unsigned long DWORD; +typedef int BOOL; +#endif + +BOOL _subprocess_windows_send_vm_close(DWORD pid); + +#endif + #endif /* process_shims_h */ diff --git a/Sources/_CShims/include/_CShimsTargetConditionals.h b/Sources/_SubprocessCShims/include/target_conditionals.h similarity index 76% rename from Sources/_CShims/include/_CShimsTargetConditionals.h rename to Sources/_SubprocessCShims/include/target_conditionals.h index 9e1d80cb..fef2eaf2 100644 --- a/Sources/_CShims/include/_CShimsTargetConditionals.h +++ b/Sources/_SubprocessCShims/include/target_conditionals.h @@ -2,13 +2,11 @@ // // 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 +// Copyright (c) 2021 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/_SubprocessCShims/process_shims.c b/Sources/_SubprocessCShims/process_shims.c new file mode 100644 index 00000000..287e1a8d --- /dev/null +++ b/Sources/_SubprocessCShims/process_shims.c @@ -0,0 +1,682 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "include/target_conditionals.h" + +#if TARGET_OS_LINUX +// For posix_spawn_file_actions_addchdir_np +#define _GNU_SOURCE 1 +#endif + +#include "include/process_shims.h" + +#if TARGET_OS_WINDOWS +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if __has_include() +#include +#elif defined(_WIN32) +#include +#elif __has_include() +#include +extern char **environ; +#endif + +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 + +#if __has_include() +vm_size_t _subprocess_vm_size(void) { + // This shim exists because vm_page_size is not marked const, and therefore looks like global mutable state to Swift. + return vm_page_size; +} +#endif + +// MARK: - Darwin (posix_spawn) +#if TARGET_OS_MAC +static int _subprocess_spawn_prefork( + 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 +) { + // Set `POSIX_SPAWN_SETEXEC` flag since we are forking ourselves + 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; + } + // Setup pipe to catch exec failures from child + int pipefd[2]; + if (pipe(pipefd) != 0) { + return errno; + } + // Set FD_CLOEXEC so the pipe is automatically closed when exec succeeds + flags = fcntl(pipefd[0], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[0], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + flags = fcntl(pipefd[1], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[1], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + // Finally, fork +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" + pid_t childPid = fork(); +#pragma GCC diagnostic pop + if (childPid == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + if (childPid == 0) { + // Child process + close(pipefd[0]); // Close unused read end + + // Perform setups + if (number_of_sgroups > 0 && sgroups != NULL) { + if (setgroups(number_of_sgroups, sgroups) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (uid != NULL) { + if (setuid(*uid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (gid != NULL) { + if (setgid(*gid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (create_session != 0) { + (void)setsid(); + } + + // Use posix_spawnas exec + int error = posix_spawn(pid, exec_path, file_actions, spawn_attrs, args, env); + // If we reached this point, something went wrong + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } else { + // Parent process + close(pipefd[1]); // Close unused write end + // Communicate child pid back + *pid = childPid; + // Read from the pipe until pipe is closed + // Eitehr due to exec succeeds or error is written + int childError = 0; + if (read(pipefd[0], &childError, sizeof(childError)) > 0) { + // We encountered error + close(pipefd[0]); + return childError; + } else { + // Child process exec was successful + close(pipefd[0]); + return 0; + } + } +} + +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) { + int rc = _subprocess_spawn_prefork( + pid, + exec_path, + file_actions, spawn_attrs, + args, env, + uid, gid, number_of_sgroups, sgroups, create_session + ); + 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 TARGET_OS_LINUX + +#if _POSIX_SPAWN +static int _subprocess_is_addchdir_np_available() { +#if defined(__GLIBC__) && !__GLIBC_PREREQ(2, 29) + // Glibc versions prior to 2.29 don't support posix_spawn_file_actions_addchdir_np, impacting: + // - Amazon Linux 2 (EoL mid-2025) + return 0; +#elif defined(__OpenBSD__) || defined(__QNX__) + // Currently missing as of: + // - OpenBSD 7.5 (April 2024) + // - QNX 8 (December 2023) + return 0; +#elif defined(__GLIBC__) || TARGET_OS_DARWIN || defined(__FreeBSD__) || (defined(__ANDROID__) && __ANDROID_API__ >= 34) || defined(__musl__) + // Pre-standard posix_spawn_file_actions_addchdir_np version available in: + // - Solaris 11.3 (October 2015) + // - Glibc 2.29 (February 2019) + // - macOS 10.15 (October 2019) + // - musl 1.1.24 (October 2019) + // - FreeBSD 13.1 (May 2022) + // - Android 14 (October 2023) + return 1; +#else + // Standardized posix_spawn_file_actions_addchdir version (POSIX.1-2024, June 2024) available in: + // - Solaris 11.4 (August 2018) + // - NetBSD 10.0 (March 2024) + return 1; +#endif +} + +static int _subprocess_addchdir_np( + posix_spawn_file_actions_t *file_actions, + const char * __restrict path +) { +#if defined(__GLIBC__) && !__GLIBC_PREREQ(2, 29) + // Glibc versions prior to 2.29 don't support posix_spawn_file_actions_addchdir_np, impacting: + // - Amazon Linux 2 (EoL mid-2025) + // noop +#elif defined(__OpenBSD__) || defined(__QNX__) + // Currently missing as of: + // - OpenBSD 7.5 (April 2024) + // - QNX 8 (December 2023) + // noop +#elif defined(__GLIBC__) || TARGET_OS_DARWIN || defined(__FreeBSD__) || (defined(__ANDROID__) && __ANDROID_API__ >= 34) || defined(__musl__) + // Pre-standard posix_spawn_file_actions_addchdir_np version available in: + // - Solaris 11.3 (October 2015) + // - Glibc 2.29 (February 2019) + // - macOS 10.15 (October 2019) + // - musl 1.1.24 (October 2019) + // - FreeBSD 13.1 (May 2022) + // - Android 14 (October 2023) + return posix_spawn_file_actions_addchdir_np(file_actions, path); +#else + // Standardized posix_spawn_file_actions_addchdir version (POSIX.1-2024, June 2024) available in: + // - Solaris 11.4 (August 2018) + // - NetBSD 10.0 (March 2024) + return posix_spawn_file_actions_addchdir(file_actions, path); +#endif +} + +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; } + } + // Setup working directory + rc = _subprocess_addchdir_np(&file_actions, working_directory); + 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 = _subprocess_is_addchdir_np_available() == 0 || + 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 + + // Setup pipe to catch exec failures from child + int pipefd[2]; + if (pipe(pipefd) != 0) { + return errno; + } + // Set FD_CLOEXEC so the pipe is automatically closed when exec succeeds + short flags = fcntl(pipefd[0], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[0], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + flags = fcntl(pipefd[1], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[1], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + // Finally, fork +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" + pid_t childPid = fork(); +#pragma GCC diagnostic pop + if (childPid == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + if (childPid == 0) { + // Child process + close(pipefd[0]); // Close unused read end + + // Perform setups + if (working_directory != NULL) { + if (chdir(working_directory) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + + if (uid != NULL) { + if (setuid(*uid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (gid != NULL) { + if (setgid(*gid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (number_of_sgroups > 0 && sgroups != NULL) { + if (setgroups(number_of_sgroups, sgroups) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + 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) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + if (file_descriptors[2] >= 0) { + rc = dup2(file_descriptors[2], STDOUT_FILENO); + if (rc < 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + if (file_descriptors[4] >= 0) { + rc = dup2(file_descriptors[4], STDERR_FILENO); + if (rc < 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + // 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) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + // Run custom configuratior + if (configurator != NULL) { + configurator(); + } + // Finally, exec + execve(exec_path, args, env); + // If we reached this point, something went wrong + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } else { + // Parent process + close(pipefd[1]); // Close unused write end + // Communicate child pid back + *pid = childPid; + // Read from the pipe until pipe is closed + // Eitehr due to exec succeeds or error is written + int childError = 0; + if (read(pipefd[0], &childError, sizeof(childError)) > 0) { + // We encountered error + close(pipefd[0]); + return childError; + } else { + // Child process exec was successful + close(pipefd[0]); + return 0; + } + } +} + +#endif // TARGET_OS_LINUX + +#endif // !TARGET_OS_WINDOWS + +#pragma mark - Environment Locking + +#if __has_include() +#import +void _subprocess_lock_environ(void) { + environ_lock_np(); +} + +void _subprocess_unlock_environ(void) { + environ_unlock_np(); +} +#else +void _subprocess_lock_environ(void) { /* noop */ } +void _subprocess_unlock_environ(void) { /* noop */ } +#endif + +char ** _subprocess_get_environ(void) { +#if __has_include() + return *_NSGetEnviron(); +#elif defined(_WIN32) +#include + return _environ; +#elif TARGET_OS_WASI + return __wasilibc_get_environ(); +#elif __has_include() + return environ; +#endif +} + + +#if TARGET_OS_WINDOWS + +typedef struct { + DWORD pid; + HWND mainWindow; +} CallbackContext; + +static BOOL CALLBACK enumWindowsCallback( + HWND hwnd, + LPARAM lParam +) { + CallbackContext *context = (CallbackContext *)lParam; + DWORD pid; + GetWindowThreadProcessId(hwnd, &pid); + if (pid == context->pid) { + context->mainWindow = hwnd; + return FALSE; // Stop enumeration + } + return TRUE; // Continue enumeration +} + +BOOL _subprocess_windows_send_vm_close( + DWORD pid +) { + // First attempt to find the Window associate + // with this process + CallbackContext context = {0}; + context.pid = pid; + EnumWindows(enumWindowsCallback, (LPARAM)&context); + + if (context.mainWindow != NULL) { + if (SendMessage(context.mainWindow, WM_CLOSE, 0, 0)) { + return TRUE; + } + } + + return FALSE; +} + +#endif + diff --git a/SwiftKit/build.gradle b/SwiftKit/build.gradle index e0896e66..8f3b6c51 100644 --- a/SwiftKit/build.gradle +++ b/SwiftKit/build.gradle @@ -25,7 +25,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) + languageVersion.set(JavaLanguageVersion.of(24)) } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java index dea52154..8d2d7ad2 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java @@ -41,8 +41,8 @@ public static long addressByteSize() { 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 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); /** diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java index 53680f5f..2a031b7e 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java @@ -71,7 +71,6 @@ public static MemorySegment fullTypeMetadata(MemorySegment typeMetadata) { public static MemorySegment valueWitnessTable(MemorySegment typeMetadata) { return fullTypeMetadata(typeMetadata) .get(SwiftValueLayout.SWIFT_POINTER, SwiftValueWitnessTable.fullTypeMetadata$vwt$offset); -// .get(ValueLayout.ADDRESS, SwiftValueWitnessTable.fullTypeMetadata$vwt$offset); } diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java b/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java index ffefe72e..3419405e 100644 --- a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java +++ b/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java @@ -20,42 +20,42 @@ public class SwiftRuntimeMetadataTest { - @Test - public void integer_layout_metadata() { - SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("Si").get(); - - if (SwiftValueLayout.addressByteSize() == 4) { - // 32-bit platform - Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[9:b1]x7](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } else { - // 64-bit platform - Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[8:b1]](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } - } - - @Test - public void optional_integer_layout_metadata() { - SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("SiSg").get(); - - if (SwiftValueLayout.addressByteSize() == 4) { - // 64-bit platform - Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } else { - // 64-bit platform - Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } - } +// @Test +// public void integer_layout_metadata() { +// SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("Si").get(); +// +// if (SwiftValueLayout.addressByteSize() == 4) { +// // 32-bit platform +// Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[9:b1]x7](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } else { +// // 64-bit platform +// Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[8:b1]](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } +// } +// +// @Test +// public void optional_integer_layout_metadata() { +// SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("SiSg").get(); +// +// if (SwiftValueLayout.addressByteSize() == 4) { +// // 64-bit platform +// Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } else { +// // 64-bit platform +// Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } +// } } diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index f017bc49..05323630 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// @_spi(Testing) import JExtractSwiftLib +import JavaKitConfigurationShared import SwiftSyntax import Testing @@ -31,9 +32,9 @@ func assertLoweredFunction( line: Int = #line, column: Int = #column ) throws { - let translator = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let translator = Swift2JavaTranslator(config: config) if let sourceFile { translator.add(filePath: "Fake.swift", text: sourceFile) @@ -117,9 +118,9 @@ func assertLoweredVariableAccessor( line: Int = #line, column: Int = #column ) throws { - let translator = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let translator = Swift2JavaTranslator(config: config) if let sourceFile { translator.add(filePath: "Fake.swift", text: sourceFile) diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index b3a09a22..3c4ad56a 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -25,7 +25,7 @@ enum RenderKind { func assertOutput( dump: Bool = false, input: String, - _ mode: GenerationMode, + _ mode: JExtractGenerationMode, _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", detectChunkByInitialLines: Int = 4, @@ -35,7 +35,9 @@ func assertOutput( line: Int = #line, column: Int = #column ) throws { - let translator = Swift2JavaTranslator(swiftModuleName: swiftModuleName) + var config = Configuration() + config.swiftModule = swiftModuleName + let translator = Swift2JavaTranslator(config: config) try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index c738914a..d81dad8c 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import JavaKitConfigurationShared import Testing final class FuncCallbackImportTests { @@ -36,9 +37,9 @@ final class FuncCallbackImportTests { @Test("Import: public func callMe(callback: () -> Void)") func func_callMeFunc_callback() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) @@ -125,9 +126,9 @@ final class FuncCallbackImportTests { @Test("Import: public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())") func func_callMeMoreFunc_callback() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) @@ -240,9 +241,9 @@ final class FuncCallbackImportTests { @Test("Import: public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)") func func_withBuffer_body() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 2cac6218..7af3c706 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import JavaKitConfigurationShared import Testing @Suite @@ -233,9 +234,9 @@ extension FunctionDescriptorTests { logLevel: Logger.Level = .trace, body: (String) throws -> Void ) throws { - let st = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) @@ -266,9 +267,9 @@ extension FunctionDescriptorTests { logLevel: Logger.Level = .trace, body: (String) throws -> Void ) throws { - let st = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 2405e0ac..36610dcd 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import JavaKitConfigurationShared import Testing final class MethodImportTests { @@ -64,9 +65,9 @@ final class MethodImportTests { @Test("Import: public func helloWorld()") func method_helloWorld() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -103,9 +104,9 @@ final class MethodImportTests { @Test("Import: public func globalTakeInt(i: Int)") func func_globalTakeInt() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -144,9 +145,9 @@ final class MethodImportTests { @Test("Import: public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)") func func_globalTakeIntLongString() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -188,9 +189,9 @@ final class MethodImportTests { @Test("Import: public func globalReturnClass() -> MySwiftClass") func func_globalReturnClass() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -232,9 +233,9 @@ final class MethodImportTests { @Test("Import: func swapRawBufferPointer(buffer: _)") func func_globalSwapRawBufferPointer() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -279,9 +280,9 @@ final class MethodImportTests { @Test func method_class_helloMemberFunction() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -321,9 +322,9 @@ final class MethodImportTests { @Test func method_class_makeInt() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -363,9 +364,9 @@ final class MethodImportTests { @Test func class_constructor() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -406,9 +407,10 @@ final class MethodImportTests { @Test func struct_constructor() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) + st.log.logLevel = .info try st.analyze(file: "Fake.swift", text: class_interfaceFile) diff --git a/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift b/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift new file mode 100644 index 00000000..dbd05f66 --- /dev/null +++ b/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaKitConfigurationShared +import Testing + +@Suite +struct GradleDependencyParsingTests { + + @Test + func parseSingleDependency() throws { + let inputString = "com.example:thing:12.2" + let parsed: JavaDependencyDescriptor = parseDependencyDescriptor(inputString)! + + #expect(parsed.groupID == "com.example") + #expect(parsed.artifactID == "thing") + #expect(parsed.version == "12.2") + } + + @Test + func parseMultiple() throws { + let inputString = "com.example:thing:12.2,com.example:another:1.2.3-beta.1," + let parsed: [JavaDependencyDescriptor] = parseDependencyDescriptors(inputString) + + #expect(parsed.count == 2) + #expect(parsed[0].groupID == "com.example") + #expect(parsed[0].artifactID == "thing") + #expect(parsed[0].version == "12.2") + #expect(parsed[1].groupID == "com.example") + #expect(parsed[1].artifactID == "another") + #expect(parsed[1].version == "1.2.3-beta.1") + } +} + diff --git a/docker/Dockerfile b/docker/Dockerfile index c68ccc3c..6604d091 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ ARG swift_version=nightly-main ARG ubuntu_version=jammy -ARG base_image=docker.io/swiftlang/swift:$swift_version-$ubuntu_version +ARG base_image=docker.io/swiftlang/swift:${swift_version}-${ubuntu_version} FROM $base_image # needed to do again after FROM due to docker limitation ARG swift_version @@ -18,13 +18,9 @@ ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US.UTF-8 -# JDK dependency COPY install_jdk.sh . + +# JDK dependency RUN bash -xc 'JDK_VENDOR=Corretto ./install_jdk.sh' ENV JAVA_HOME="/usr/lib/jvm/default-jdk" ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" - -# Install "untested" nightly 'main' Swift -# TODO: Only do this if the released Swift is older than what we require -#COPY install_untested_nightly_swift.sh . -RUN #bash -xc './install_untested_nightly_swift.sh' diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8aa295ce..8d0ddda3 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -14,70 +14,101 @@ ##===----------------------------------------------------------------------===## set -euo pipefail -# Supported JDKs: Corretto or OpenJDK +# We need JDK 24 because that's the supported version with latest FFM +# However, we also need JDK 23 at most because Gradle does not support 24. + +# Supported JDKs: Corretto if [ "$JDK_VENDOR" = "" ]; then declare -r JDK_VENDOR="Corretto" fi -echo "Installing $JDK_VENDOR JDK..." -apt-get update && apt-get install -y wget +apt-get update && apt-get install -y wget tree echo "Download JDK for: $(uname -m)" -if [ "$JDK_VENDOR" = 'OpenJDK' ]; then - if [ "$(uname -m)" = 'aarch64' ]; then - declare -r JDK_URL="https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz" - declare -r EXPECT_JDK_SHA="076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7" - else - declare -r JDK_URL="https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/$JDK_NAME" - declare -r EXPECT_JDK_SHA="08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67" - fi +download_and_install_jdk() { + local jdk_version="$1" + local jdk_url="" + local expected_md5="" - wget -q -O jdk.tar.gz "$JDK_URL" + echo "Installing $JDK_VENDOR JDK (${jdk_version})..." - declare JDK_SHA # on separate lines due to: SC2155 (warning): Declare and assign separately to avoid masking return values. - JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" - if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then - echo "Downloaded JDK SHA does not match expected!" - echo "Expected: $EXPECT_JDK_SHA" - echo " Was: $JDK_SHA" - exit 1; - else - echo "JDK SHA is correct."; - fi -elif [ "$JDK_VENDOR" = 'Corretto' ]; then - if [ "$(uname -m)" = 'aarch64' ]; then - declare -r JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-22-aarch64-linux-jdk.tar.gz" - declare -r EXPECT_JDK_MD5="1ebe5f5229bb18bc784a1e0f54d3fe39" - else - declare -r JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-22-x64-linux-jdk.tar.gz" - declare -r EXPECT_JDK_MD5="5bd7fe30eb063699a3b4db7a00455841" - fi + if [ "$JDK_VENDOR" = 'Corretto' ]; then + if [ "$(uname -m)" = 'aarch64' ]; then + case "$jdk_version" in + "21") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-aarch64-linux-jdk.tar.gz" + expected_md5="87e458029cf9976945dfa3a22af3f850" + ;; + "24") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-aarch64-linux-jdk.tar.gz" + expected_md5="3b543f4e971350b73d0ab6d8174cc030" + ;; + *) + echo "Unsupported JDK version: '$jdk_version'" + exit 1 + ;; + esac + else + case "$jdk_version" in + "21") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-x64-linux-jdk.tar.gz" + expected_md5="a123e7f50807c27de521bef7378d3377" + ;; + "24") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-x64-linux-jdk.tar.gz" + expected_md5="130885ded3cbfc712fbe9f7dace45a52" + ;; + *) + echo "Unsupported JDK version: '$jdk_version'" + exit 1 + ;; + esac + fi + else + echo "Unsupported JDK vendor: '$JDK_VENDOR'" + exit 1 + fi - wget -q -O jdk.tar.gz "$JDK_URL" + # Download JDK + local jdk_filename="jdk_${jdk_version}.tar.gz" + wget -q -O "$jdk_filename" "$jdk_url" - declare JDK_MD5 # on separate lines due to: SC2155 (warning): Declare and assign separately to avoid masking return values. - JDK_MD5="$(md5sum jdk.tar.gz | cut -d ' ' -f 1)" - if [ "$JDK_MD5" != "$EXPECT_JDK_MD5" ]; then - echo "Downloaded JDK MD5 does not match expected!" - echo "Expected: $EXPECT_JDK_MD5" - echo " Was: $JDK_MD5" - exit 1; - else - echo "JDK MD5 is correct."; - fi -else - echo "Unsupported JDK vendor: '$JDK_VENDOR'" - exit 1 -fi + # Verify MD5 + local jdk_md5 + jdk_md5="$(md5sum "$jdk_filename" | cut -d ' ' -f 1)" + if [ "$jdk_md5" != "$expected_md5" ]; then + echo "Downloaded JDK $jdk_version MD5 does not match expected!" + echo "Expected: $expected_md5" + echo " Was: $jdk_md5" + exit 1 + else + echo "JDK $jdk_version MD5 is correct." + fi -# Extract and verify the JDK installation + # Extract and install JDK + mkdir -p "/usr/lib/jvm/jdk-${jdk_version}" + mv "$jdk_filename" "/usr/lib/jvm/jdk-${jdk_version}/" + cd "/usr/lib/jvm/jdk-${jdk_version}/" || exit 1 + tar xzf "$jdk_filename" && rm "$jdk_filename" -mkdir -p /usr/lib/jvm/ -mv jdk.tar.gz /usr/lib/jvm/ -cd /usr/lib/jvm/ -tar xzvf jdk.tar.gz && rm jdk.tar.gz -mv "$(find . -depth -maxdepth 1 -type d | head -n1)" default-jdk + # Move extracted directory to a standard name + local extracted_dir + extracted_dir="$(find . -maxdepth 1 -type d -name '*linux*' | head -n1)" + echo "move $extracted_dir to $(pwd)..." + mv "${extracted_dir}"/* . + echo "JDK $jdk_version installed successfully in /usr/lib/jvm/jdk-${jdk_version}/" + cd "$HOME" +} + +# Usage: Install both JDK versions +download_and_install_jdk "21" +download_and_install_jdk "24" + +ls -la /usr/lib/jvm/ +cd /usr/lib/jvm/ +ln -s jdk-21 default-jdk +find . | grep java | grep bin echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/docker/install_untested_nightly_swift.sh b/docker/install_untested_nightly_swift.sh deleted file mode 100755 index 9cb920ab..00000000 --- a/docker/install_untested_nightly_swift.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## 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 -## -##===----------------------------------------------------------------------===## -set -euo pipefail - -echo "Download [nightly] [untested] Swift toolchain for: $(uname -m)" - -ARCH="$(arch)" -if [[ "$ARCH" = "i386" || "$ARCH" = "x86_64" ]]; then - SWIFT_UNTESTED_TOOLCHAIN_JOB_URL="https://ci.swift.org/job/oss-swift-package-ubuntu-22_04/lastSuccessfulBuild/consoleText" -else - SWIFT_UNTESTED_TOOLCHAIN_JOB_URL="https://ci.swift.org/job/oss-swift-package-ubuntu-22_04-aarch64/lastSuccessfulBuild/consoleText" -fi - -if [[ "$(grep "22.04" /etc/lsb-release)" = "" ]]; then - echo "This script specifically only supports Ubuntu 22.04 due to nightly toolchain availability" - exit 1 -fi - -UNTESTED_TOOLCHAIN_URL=$(curl -s $SWIFT_UNTESTED_TOOLCHAIN_JOB_URL | grep 'Toolchain: ' | sed 's/Toolchain: //g') -UNTESTED_TOOLCHAIN_FILENAME=$"toolchain.tar.gz" - -echo "Download toolchain: $UNTESTED_TOOLCHAIN_URL" - -cd / -curl -s "$UNTESTED_TOOLCHAIN_URL" > "$UNTESTED_TOOLCHAIN_FILENAME" - -swift -version - -echo "Extract toolchain: $UNTESTED_TOOLCHAIN_FILENAME" -tar xzf "$UNTESTED_TOOLCHAIN_FILENAME" -swift -version From e66e490f173e4cdfe7fda45147dccf85c451ddcd Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 25 Jun 2025 11:14:42 +0900 Subject: [PATCH 077/178] CI: Correct validation & bring back pointer "max" bounds workaround (#284) --- .github/scripts/validate_sample.sh | 14 +++++++------- Samples/SwiftKitSampleApp/ci-validate.sh | 6 ++++++ .../java/org/swift/swiftkit/SwiftValueLayout.java | 6 ++++-- 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100755 Samples/SwiftKitSampleApp/ci-validate.sh diff --git a/.github/scripts/validate_sample.sh b/.github/scripts/validate_sample.sh index 7e0ab3d2..b5780976 100755 --- a/.github/scripts/validate_sample.sh +++ b/.github/scripts/validate_sample.sh @@ -1,5 +1,8 @@ #!/bin/bash +set -e +set -x + # shellcheck disable=SC2034 declare -r GREEN='\033[0;32m' declare -r BOLD='\033[1m' @@ -14,14 +17,11 @@ echo "========================================================================" printf "Validate sample '${BOLD}%s${RESET}' using: " "$sampleDir" cd "$sampleDir" || exit if [[ $(find . -name ${CI_VALIDATE_SCRIPT} -maxdepth 1) ]]; then - echo -e "Custom ${BOLD}${CI_VALIDATE_SCRIPT}${RESET} script..." - ./${CI_VALIDATE_SCRIPT} || exit -elif [[ $(find . -name 'build.gradle*' -maxdepth 1) ]]; then - echo -e "${BOLD}Gradle${RESET} build..." - ./gradlew build || ./gradlew build --info # re-run to get better failure output + echo -e "Run ${BOLD}${CI_VALIDATE_SCRIPT}${RESET} script..." + ./${CI_VALIDATE_SCRIPT} else - echo -e "${BOLD}SwiftPM${RESET} build..." - swift build || exit + echo -e "${BOLD}Missing ${CI_VALIDATE_SCRIPT} file!${RESET}" + exit fi echo -e "Validated sample '${BOLD}${sampleDir}${RESET}': ${BOLD}passed${RESET}." diff --git a/Samples/SwiftKitSampleApp/ci-validate.sh b/Samples/SwiftKitSampleApp/ci-validate.sh new file mode 100755 index 00000000..07b42627 --- /dev/null +++ b/Samples/SwiftKitSampleApp/ci-validate.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -x +set -e + +./gradlew run \ No newline at end of file diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java index 8d2d7ad2..00215ef0 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java @@ -41,8 +41,10 @@ public static long addressByteSize() { 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)); + + // FIXME: this sequence layout is a workaround, we must properly size pointers when we get them. + 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); /** From 8d4e5cd0086300a0f8abd5bf212fdbbf9a0e589e Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 24 Jun 2025 19:26:07 -0700 Subject: [PATCH 078/178] [JavaRuntime] Add module.modulemap Fix a build error in 6.1 toolchain. Apparently some targets have both `JavaRuntime-tool.build` and `JavaRuntime.build` as the include paths. The auto generated `module.modulemap` in both directories cause the error `error: redefinition of module 'JavaRuntime'`. To avoid that, add `module.modulemap` in the source directory so it's not generated. Fixes: https://github.com/swiftlang/swift-java/issues/229 --- Sources/JavaRuntime/include/module.modulemap | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Sources/JavaRuntime/include/module.modulemap diff --git a/Sources/JavaRuntime/include/module.modulemap b/Sources/JavaRuntime/include/module.modulemap new file mode 100644 index 00000000..2c0d4a98 --- /dev/null +++ b/Sources/JavaRuntime/include/module.modulemap @@ -0,0 +1,4 @@ +module JavaRuntime { + umbrella header "JavaRuntime.h" + export * +} From 6e53b3b4c4420a0300d6362865418324848d8d11 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Wed, 25 Jun 2025 18:51:33 -0700 Subject: [PATCH 079/178] [JExtract] Stop using SyntaxVisitor for Swift2JavaVisitor (#290) --- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 +- .../Swift2JavaTranslator.swift | 2 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 171 ++++++++---------- 3 files changed, 77 insertions(+), 100 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index ee86fc98..0a5952f3 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -37,8 +37,8 @@ package class ImportedNominalType: ImportedDecl { self.swiftNominal = swiftNominal } - var javaClassName: String { - swiftNominal.name + var swiftType: SwiftType { + return .nominal(.init(nominalTypeDecl: swiftNominal)) } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 07d8cafb..89deda9a 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -106,7 +106,7 @@ extension Swift2JavaTranslator { for input in self.inputs { log.trace("Analyzing \(input.filePath)") - visitor.walk(input.syntax) + visitor.visit(sourceFile: input.syntax) } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 0b4d6bbf..7ce982a2 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -16,98 +16,85 @@ import Foundation import SwiftParser import SwiftSyntax -final class Swift2JavaVisitor: SyntaxVisitor { +final class Swift2JavaVisitor { let translator: Swift2JavaTranslator - /// Type context stack associated with the syntax. - var typeContext: [(syntaxID: Syntax.ID, type: ImportedNominalType)] = [] - - /// 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 } - - var log: Logger { translator.log } - init(translator: Swift2JavaTranslator) { self.translator = translator - - 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)) - } + var log: Logger { translator.log } - /// 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 + func visit(sourceFile node: SourceFileSyntax) { + for codeItem in node.statements { + if let declNode = codeItem.item.as(DeclSyntax.self) { + self.visit(decl: declNode, in: nil) + } } } - override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - log.debug("Visit \(node.kind): '\(node.qualifiedNameForDebug)'") - guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else { - return .skipChildren + func visit(decl node: DeclSyntax, in parent: ImportedNominalType?) { + switch node.as(DeclSyntaxEnum.self) { + case .actorDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .classDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .structDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .enumDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .protocolDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .extensionDecl(let node): + self.visit(extensionDecl: node, in: parent) + case .typeAliasDecl: + break // TODO: Implement + case .associatedTypeDecl: + break // TODO: Implement + + case .initializerDecl(let node): + self.visit(initializerDecl: node, in: parent) + case .functionDecl(let node): + self.visit(functionDecl: node, in: parent) + case .variableDecl(let node): + self.visit(variableDecl: node, in: parent) + case .subscriptDecl: + // TODO: Implement + break + + default: + break } - - self.pushTypeContext(syntax: node, importedNominal: importedNominalType) - return .visitChildren } - override func visitPost(_ node: ClassDeclSyntax) { - if self.popTypeContext(syntax: node) { - log.debug("Completed import: \(node.kind) \(node.name)") + func visit( + nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax, + in parent: ImportedNominalType? + ) { + guard let importedNominalType = translator.importedNominalType(node, parent: parent) else { + return } - } - - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - log.debug("Visit \(node.kind): \(node.qualifiedNameForDebug)") - guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else { - return .skipChildren + for memberItem in node.memberBlock.members { + self.visit(decl: memberItem.decl, in: importedNominalType) } - - self.pushTypeContext(syntax: node, importedNominal: importedNominalType) - return .visitChildren } - override func visitPost(_ node: StructDeclSyntax) { - if self.popTypeContext(syntax: node) { - log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)") + func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { + guard parent != nil else { + // 'extension' in a nominal type is invalid. Ignore + return } - } - - 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 importedNominalType = translator.importedNominalType(node.extendedType) else { - return .skipChildren + return } - - self.pushTypeContext(syntax: node, importedNominal: importedNominalType) - return .visitChildren - } - - override func visitPost(_ node: ExtensionDeclSyntax) { - if self.popTypeContext(syntax: node) { - log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)") + for memberItem in node.memberBlock.members { + self.visit(decl: memberItem.decl, in: importedNominalType) } } - override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { + func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { guard node.shouldImport(log: log) else { - return .skipChildren + return } self.log.debug("Import function: '\(node.qualifiedNameForDebug)'") @@ -116,12 +103,12 @@ final class Swift2JavaVisitor: SyntaxVisitor { do { signature = try SwiftFunctionSignature( node, - enclosingType: self.currentSwiftType, + enclosingType: typeContext?.swiftType, symbolTable: self.translator.symbolTable ) } catch { self.log.debug("Failed to import: '\(node.qualifiedNameForDebug)'; \(error)") - return .skipChildren + return } let imported = ImportedFunc( @@ -133,22 +120,20 @@ final class Swift2JavaVisitor: SyntaxVisitor { ) log.debug("Record imported method \(node.qualifiedNameForDebug)") - if let currentType { - currentType.methods.append(imported) + if let typeContext { + typeContext.methods.append(imported) } else { translator.importedGlobalFuncs.append(imported) } - - return .skipChildren } - override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { guard node.shouldImport(log: log) else { - return .skipChildren + return } guard let binding = node.bindings.first else { - return .skipChildren + return } let varName = "\(binding.pattern.trimmed)" @@ -159,7 +144,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { let signature = try SwiftFunctionSignature( node, isSet: kind == .setter, - enclosingType: self.currentSwiftType, + enclosingType: typeContext?.swiftType, symbolTable: self.translator.symbolTable ) @@ -170,10 +155,10 @@ final class Swift2JavaVisitor: SyntaxVisitor { apiKind: kind, functionSignature: signature ) - + log.debug("Record imported variable accessor \(kind == .getter ? "getter" : "setter"):\(node.qualifiedNameForDebug)") - if let currentType { - currentType.variables.append(imported) + if let typeContext { + typeContext.variables.append(imported) } else { translator.importedGlobalVariables.append(imported) } @@ -189,18 +174,16 @@ final class Swift2JavaVisitor: SyntaxVisitor { } } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") - return .skipChildren } - - return .skipChildren } - override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - guard let currentType else { - fatalError("Initializer must be within a current type, was: \(node)") + func visit(initializerDecl node: InitializerDeclSyntax, in typeContext: ImportedNominalType?) { + guard let typeContext else { + self.log.info("Initializer must be within a current type; \(node)") + return } guard node.shouldImport(log: log) else { - return .skipChildren + return } self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'") @@ -209,12 +192,12 @@ final class Swift2JavaVisitor: SyntaxVisitor { do { signature = try SwiftFunctionSignature( node, - enclosingType: self.currentSwiftType, + enclosingType: typeContext.swiftType, symbolTable: self.translator.symbolTable ) } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") - return .skipChildren + return } let imported = ImportedFunc( module: translator.swiftModuleName, @@ -224,13 +207,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { functionSignature: signature ) - currentType.initializers.append(imported) - - return .skipChildren - } - - override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren + typeContext.initializers.append(imported) } } From 45205e1c726804ff79491b223711af2676a85fd2 Mon Sep 17 00:00:00 2001 From: Alexander <45719053+KeoFoxy@users.noreply.github.com> Date: Fri, 27 Jun 2025 07:59:58 +0300 Subject: [PATCH 080/178] feature: Code enhancement (#267) --- .../src/test/java/org/swift/swiftkit/SwiftArenaTest.java | 4 ++-- Sources/JExtractSwiftLib/CodePrinter.swift | 4 ++-- .../JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift | 4 ++-- Sources/JExtractSwiftLib/Logger.swift | 2 +- Sources/JavaKit/JavaKitVM/LockedState.swift | 4 ++-- Sources/JavaKit/JavaValue.swift | 2 +- Sources/SwiftKitSwift/SwiftKit.swift | 2 +- .../main/java/org/swift/swiftkit/SwiftValueWitnessTable.java | 2 +- USER_GUIDE.md | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) 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 0d900a62..52d791a1 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java @@ -63,7 +63,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() { try { unsafelyEscapedOutsideArenaScope.echoIntMethod(1); - fail("Expected exception to be thrown! Object was suposed to be dead."); + fail("Expected exception to be thrown! Object was supposed to be dead."); } catch (IllegalStateException ex) { return; } @@ -82,7 +82,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() { try { unsafelyEscapedOutsideArenaScope.echoIntMethod(1); - fail("Expected exception to be thrown! Object was suposed to be dead."); + fail("Expected exception to be thrown! Object was supposed to be dead."); } catch (IllegalStateException ex) { return; } diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index 449cab3d..8c22fbee 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -53,14 +53,14 @@ public struct CodePrinter { self.mode = mode } - internal mutating func append(_ text: String) { + mutating func append(_ text: String) { contents.append(text) if self.verbose { Swift.print(text, terminator: "") } } - internal mutating func append(contentsOf text: S) + mutating func append(contentsOf text: S) where S: Sequence, S.Element == Character { contents.append(contentsOf: text) if self.verbose { diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index b732b9f8..da836d45 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -16,7 +16,7 @@ import SwiftDiagnostics import SwiftSyntax extension WithModifiersSyntax { - internal var accessControlModifiers: DeclModifierListSyntax { + var accessControlModifiers: DeclModifierListSyntax { modifiers.filter { modifier in modifier.isAccessControl } @@ -24,7 +24,7 @@ extension WithModifiersSyntax { } extension ImplicitlyUnwrappedOptionalTypeSyntax { - internal var asOptionalTypeSyntax: any TypeSyntaxProtocol { + var asOptionalTypeSyntax: any TypeSyntaxProtocol { OptionalTypeSyntax( leadingTrivia: leadingTrivia, unexpectedBeforeWrappedType, diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift index 180ffb54..541dbae4 100644 --- a/Sources/JExtractSwiftLib/Logger.swift +++ b/Sources/JExtractSwiftLib/Logger.swift @@ -111,7 +111,7 @@ extension Logger.Level: ExpressibleByArgument { } extension Logger.Level { - internal var naturalIntegralValue: Int { + var naturalIntegralValue: Int { switch self { case .trace: return 0 diff --git a/Sources/JavaKit/JavaKitVM/LockedState.swift b/Sources/JavaKit/JavaKitVM/LockedState.swift index e095668c..b3082efc 100644 --- a/Sources/JavaKit/JavaKitVM/LockedState.swift +++ b/Sources/JavaKit/JavaKitVM/LockedState.swift @@ -13,9 +13,9 @@ //===----------------------------------------------------------------------===// #if canImport(os) -internal import os +import os #if FOUNDATION_FRAMEWORK && canImport(C.os.lock) -internal import C.os.lock +import C.os.lock #endif #elseif canImport(Bionic) import Bionic diff --git a/Sources/JavaKit/JavaValue.swift b/Sources/JavaKit/JavaValue.swift index 310b54df..1bc156a0 100644 --- a/Sources/JavaKit/JavaValue.swift +++ b/Sources/JavaKit/JavaValue.swift @@ -34,7 +34,7 @@ import JavaTypes /// The protocol provides operations to bridge values in both directions: /// - `getJNIValue(in:)`: produces the JNI value (of type `JNIType`) for the /// `self` Swift value in the given JNI environment. -/// - `init(fromJNI:in:)`: intializes a Swift value from the JNI value (of +/// - `init(fromJNI:in:)`: initializes a Swift value from the JNI value (of /// type `JNIType`) in the given JNI environment. /// /// The protocol also provides hooks to tie into JNI, including operations to diff --git a/Sources/SwiftKitSwift/SwiftKit.swift b/Sources/SwiftKitSwift/SwiftKit.swift index 38b3c1c8..fb978163 100644 --- a/Sources/SwiftKitSwift/SwiftKit.swift +++ b/Sources/SwiftKitSwift/SwiftKit.swift @@ -34,7 +34,7 @@ public func _swiftjava_swift_isUniquelyReferenced(object: UnsafeMutableRawPointe @_alwaysEmitIntoClient @_transparent - internal func _swiftjava_withHeapObject( +func _swiftjava_withHeapObject( of object: AnyObject, _ body: (UnsafeMutableRawPointer) -> R ) -> R { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java index 2a031b7e..8ddff1b6 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java @@ -56,7 +56,7 @@ public abstract class SwiftValueWitnessTable { MemoryLayout.PathElement.groupElement("vwt")); /** - * Given the address of Swift type metadata for a type, return the addres + * Given the address of Swift type metadata for a type, return the address * of the "full" type metadata that can be accessed via fullTypeMetadataLayout. */ public static MemorySegment fullTypeMetadata(MemorySegment typeMetadata) { diff --git a/USER_GUIDE.md b/USER_GUIDE.md index d7d796fd..351a3d5c 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -221,7 +221,7 @@ Note that we are passing the Jar file in the `classpath` argument when initializ ### Downcasting -All Java classes available in Swift provide `is` and `as` methods to check whether an object dynamically matches another type. The `is` operation is the equivalent of Java's `instanceof` and Swift's `is` operator, and will checkin whether a given object is of the specified type, e.g., +All Java classes available in Swift provide `is` and `as` methods to check whether an object dynamically matches another type. The `is` operation is the equivalent of Java's `instanceof` and Swift's `is` operator, and will checking whether a given object is of the specified type, e.g., ```swift if myObject.is(URL.self) { @@ -626,7 +626,7 @@ The project is still very early days, however the general outline of using this - These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files - The generated Java files contain generated code for efficient native invocations. -You can then use Swift libraries in Java just by calling the apropriate methods and initializers. +You can then use Swift libraries in Java just by calling the appropriate methods and initializers. ## `jextract-swift`: Generating Java bridging files From df03e23c5ac84da6612ed6aa9cfe34f24bdbcbfb Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Jun 2025 14:26:30 +0900 Subject: [PATCH 081/178] ci: cleanup samples testing using matrix (#292) --- .github/workflows/pull_request.yml | 114 +++-------------------------- 1 file changed, 12 insertions(+), 102 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 39fa7ee6..c1af9ca7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -62,8 +62,8 @@ jobs: - name: Swift Test run: "swift test" - verify-sample-01: - name: Verify Sample JavaDependencySampleApp (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + verify-samples: + name: Verify Sample ${{ matrix.sample_app }} (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) runs-on: ubuntu-latest strategy: fail-fast: false @@ -71,6 +71,14 @@ jobs: swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] + sample_app: [ + 'JavaDependencySampleApp', + 'JavaKitSampleApp', + 'JavaProbablyPrime', + 'JavaSieve', + 'SwiftAndJavaJarSampleLib', + 'SwiftKitSampleApp', + ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: @@ -79,103 +87,5 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: "Verify Sample: JavaDependencySampleApp" - run: .github/scripts/validate_sample.sh Samples/JavaDependencySampleApp - 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: ['6.1.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 - 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: ['6.1.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 - 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: ['6.1.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 - 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: ['6.1.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 - 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: ['6.1.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 - # run: "swift package --package-path Benchmarks/ benchmark list" + - name: "Verify sample" + run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }} From 8c38d1918cb74b9b6c9f87059a579050f4e0893c Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 30 Jun 2025 17:20:13 +0200 Subject: [PATCH 082/178] Add JExtract JNI sample app (#295) Co-authored-by: Konrad `ktoso` Malawski --- .github/workflows/pull_request.yml | 1 + Samples/JExtractJNISampleApp/Package.swift | 79 +++++++ .../MySwiftLibrary/MySwiftLibrary.swift | 60 ++++++ .../Sources/MySwiftLibrary/swift-java.config | 4 + Samples/JExtractJNISampleApp/build.gradle | 195 ++++++++++++++++++ Samples/JExtractJNISampleApp/ci-validate.sh | 6 + Samples/JExtractJNISampleApp/gradlew | 1 + Samples/JExtractJNISampleApp/gradlew.bat | 1 + .../com/example/swift/HelloJava2SwiftJNI.java | 45 ++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JNI/JNISwift2JavaGenerator.swift | 108 +++++++--- .../JNI/JNIModuleTests.swift | 7 +- 12 files changed, 482 insertions(+), 27 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Package.swift create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config create mode 100644 Samples/JExtractJNISampleApp/build.gradle create mode 100755 Samples/JExtractJNISampleApp/ci-validate.sh create mode 120000 Samples/JExtractJNISampleApp/gradlew create mode 120000 Samples/JExtractJNISampleApp/gradlew.bat create mode 100644 Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c1af9ca7..c9d197e7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -78,6 +78,7 @@ jobs: 'JavaSieve', 'SwiftAndJavaJarSampleLib', 'SwiftKitSampleApp', + 'JExtractJNISampleApp' ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} diff --git a/Samples/JExtractJNISampleApp/Package.swift b/Samples/JExtractJNISampleApp/Package.swift new file mode 100644 index 00000000..8c7ce856 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Package.swift @@ -0,0 +1,79 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import CompilerPluginSupport +import PackageDescription + +import class Foundation.FileManager +import class Foundation.ProcessInfo + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#else + // TODO: Handle windows as well + #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#endif + +let package = Package( + name: "JExtractJNISampleApp", + platforms: [ + .macOS(.v15) + ], + products: [ + .library( + name: "MySwiftLibrary", + type: .dynamic, + targets: ["MySwiftLibrary"] + ) + + ], + dependencies: [ + .package(name: "swift-java", path: "../../") + ], + targets: [ + .target( + name: "MySwiftLibrary", + dependencies: [ + .product(name: "JavaKit", package: "swift-java"), + .product(name: "JavaRuntime", package: "swift-java"), + .product(name: "SwiftKitSwift", package: "swift-java"), + ], + exclude: [ + "swift-java.config" + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]), + ], + plugins: [ + .plugin(name: "JExtractSwiftPlugin", package: "swift-java") + ] + ) + ] +) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift new file mode 100644 index 00000000..be04bc08 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// 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 func helloWorld() { + p("\(#function)") +} + +public func globalTakeInt(i: Int64) { + p("i:\(i)") +} + +public func globalMakeInt() -> Int64 { + return 42 +} + +public func globalWriteString(string: String) -> Int64 { + return Int64(string.count) +} + +public func globalTakeIntInt(i: Int64, j: Int64) { + p("i:\(i), j:\(j)") +} + +// ==== Internal helpers + +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/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config new file mode 100644 index 00000000..46bf1f1c --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -0,0 +1,4 @@ +{ + "javaPackage": "com.example.swift", + "mode": "jni" +} diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/JExtractJNISampleApp/build.gradle new file mode 100644 index 00000000..d0e32857 --- /dev/null +++ b/Samples/JExtractJNISampleApp/build.gradle @@ -0,0 +1,195 @@ +//===----------------------------------------------------------------------===// +// +// 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 groovy.json.JsonSlurper +import org.swift.swiftkit.gradle.BuildUtils + +import java.nio.file.* +import kotlinx.serialization.json.* + +plugins { + id("build-logic.java-application-conventions") + id("me.champeau.jmh") version "0.7.2" +} + +group = "org.swift.swiftkit" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(24)) + } +} + +def swiftProductsWithJExtractPlugin() { + def stdout = new ByteArrayOutputStream() + def stderr = new ByteArrayOutputStream() + + 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 [] + } +} + + +def swiftCheckValid = tasks.register("swift-check-valid", Exec) { + commandLine "swift" + args("-version") +} + +def jextract = tasks.register("jextract", Exec) { + 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")) + + // 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 + if (!baseSwiftPluginOutputsDir.exists()) { + baseSwiftPluginOutputsDir.mkdirs() + } + Files.walk(layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile.toPath()).each { + // Add any Java sources generated by the plugin to our sourceSet + if (it.endsWith("JExtractSwiftPlugin/src/generated/java")) { + outputs.dir(it) + } + } + + workingDir = layout.projectDirectory + commandLine "swift" + 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 +sourceSets { + main { + java { + srcDir(jextract) + } + } + test { + java { + srcDir(jextract) + } + } + jmh { + java { + srcDir(jextract) + } + } +} + +tasks.build { + dependsOn("jextract") +} + + +def cleanSwift = tasks.register("cleanSwift", Exec) { + workingDir = layout.projectDirectory + commandLine "swift" + args("package", "clean") +} +tasks.clean { + dependsOn("cleanSwift") +} + +dependencies { + implementation(project(':SwiftKit')) + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +tasks.named('test', Test) { + useJUnitPlatform() +} + +application { + mainClass = "com.example.swift.HelloJava2SwiftJNI" + + applicationDefaultJvmArgs = [ + "--enable-native-access=ALL-UNNAMED", + + // Include the library paths where our dylibs are that we want to load and call + "-Djava.library.path=" + + (BuildUtils.javaLibraryPaths(rootDir) + + BuildUtils.javaLibraryPaths(project.projectDir)).join(":"), + + + // Enable tracing downcalls (to Swift) + "-Djextract.trace.downcalls=true" + ] +} + +String jmhIncludes = findProperty("jmhIncludes") + +jmh { + if (jmhIncludes != null) { + includes = [jmhIncludes] + } + + jvmArgsAppend = [ + "--enable-native-access=ALL-UNNAMED", + + "-Djava.library.path=" + + (BuildUtils.javaLibraryPaths(rootDir) + + BuildUtils.javaLibraryPaths(project.projectDir)).join(":"), + + // Enable tracing downcalls (to Swift) + "-Djextract.trace.downcalls=false" + ] +} diff --git a/Samples/JExtractJNISampleApp/ci-validate.sh b/Samples/JExtractJNISampleApp/ci-validate.sh new file mode 100755 index 00000000..07b42627 --- /dev/null +++ b/Samples/JExtractJNISampleApp/ci-validate.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -x +set -e + +./gradlew run \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/gradlew b/Samples/JExtractJNISampleApp/gradlew new file mode 120000 index 00000000..343e0d2c --- /dev/null +++ b/Samples/JExtractJNISampleApp/gradlew @@ -0,0 +1 @@ +../../gradlew \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/gradlew.bat b/Samples/JExtractJNISampleApp/gradlew.bat new file mode 120000 index 00000000..cb5a9464 --- /dev/null +++ b/Samples/JExtractJNISampleApp/gradlew.bat @@ -0,0 +1 @@ +../../gradlew.bat \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java new file mode 100644 index 00000000..36bad93c --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +// Import swift-extract generated sources + +// Import javakit/swiftkit support libraries + +import org.swift.swiftkit.SwiftKit; + +public class HelloJava2SwiftJNI { + + public static void main(String[] args) { + System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath()); + + examples(); + } + + static void examples() { + MySwiftLibrary.helloWorld(); + + MySwiftLibrary.globalTakeInt(1337); + MySwiftLibrary.globalTakeIntInt(1337, 42); + + long cnt = MySwiftLibrary.globalWriteString("String from Java"); + SwiftKit.trace("count = " + cnt); + + long i = MySwiftLibrary.globalMakeInt(); + SwiftKit.trace("globalMakeInt() = " + i); + + System.out.println("DONE."); + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 54ce0d72..d6b09b24 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -27,7 +27,7 @@ extension FFMSwift2JavaGenerator { var printer = CodePrinter() printer.print("// Empty file generated on purpose") - try printer.writeContents( + _ = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: expectedFileName) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 8ec4b437..0f16e697 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -28,6 +28,10 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var thunkNameRegistry = ThunkNameRegistry() + /// 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, javaPackage: String, @@ -40,11 +44,31 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.javaPackage = javaPackage self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory + + // 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 { try writeSwiftThunkSources() try writeExportedJavaSources() + + let pendingFileCount = self.expectedOutputSwiftFiles.count + if pendingFileCount > 0 { + print("[swift-java] Write empty [\(pendingFileCount)] 'expected' files in: \(swiftOutputDirectory)/") + try writeSwiftExpectedEmptySources() + } } } @@ -73,6 +97,19 @@ extension JNISwift2JavaGenerator { try writeSwiftThunkSources(&printer) } + package func writeSwiftExpectedEmptySources() throws { + for expectedFileName in self.expectedOutputSwiftFiles { + logger.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" @@ -84,10 +121,11 @@ extension JNISwift2JavaGenerator { if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, - javaPackagePath: javaPackagePath, + javaPackagePath: nil, filename: moduleFilename ) { print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(moduleFilename) } } catch { logger.warning("Failed to write to Swift thunks: \(moduleFilename)") @@ -113,18 +151,22 @@ extension JNISwift2JavaGenerator { 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 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 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)" : "" + let thunkReturnType = + !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" printer.printBraceBlock( """ @@ -132,24 +174,28 @@ extension JNISwift2JavaGenerator { func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) """ ) { printer in - let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { originalParam, translatedParam in - let label = originalParam.argumentLabel.map { "\($0): "} ?? "" + let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { + originalParam, translatedParam in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" } let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" + let functionDowncall = + "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" - let innerBody = if swiftReturnType.isVoid { - functionDowncall - } else { - """ - let result = \(functionDowncall) - return result.getJNIValue(in: environment)") - """ - } + 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" : "" + let dummyReturn = + !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" printer.print( """ do { @@ -173,6 +219,16 @@ extension JNISwift2JavaGenerator { printPackage(&printer) printModuleClass(&printer) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + static { + System.loadLibrary(LIB_NAME); + } + """ + ) + for decl in analysis.importedGlobalFuncs { self.logger.trace("Print global function: \(decl)") printFunctionBinding(&printer, decl) @@ -223,7 +279,9 @@ extension JNISwift2JavaGenerator { */ """ ) - printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);") + printer.print( + "public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);" + ) } } @@ -253,7 +311,7 @@ extension SwiftStandardLibraryTypeKind { var javaType: JavaType? { switch self { case .bool: .boolean - case .int: .long // TODO: Handle 32-bit or 64-bit + case .int: .long // TODO: Handle 32-bit or 64-bit case .int8: .byte case .uint16: .char case .int16: .short @@ -264,10 +322,10 @@ extension SwiftStandardLibraryTypeKind { case .void: .void case .string: .javaLangString case .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer: nil } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 2f73cca4..e483a4dd 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -44,6 +44,11 @@ struct JNIModuleTests { package com.example.swift; public final class SwiftModule { + static final String LIB_NAME = "SwiftModule"; + + static { + System.loadLibrary(LIB_NAME); + } """ ]) } @@ -104,7 +109,7 @@ struct JNIModuleTests { @_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)") + return result.getJNIValue(in: environment) } """, """ From ac562975f3e011428f8ddb34b7b23c6fb9bfde2d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Jul 2025 08:47:45 +0900 Subject: [PATCH 083/178] ci: simplistic way to get macOS running (#294) --- .github/actions/prepare_env/action.yml | 50 ++++---- .github/scripts/install_swiftly.sh | 88 ++++++++++++++ .github/scripts/validate_sample.sh | 2 + .github/workflows/pull_request.yml | 110 +++++++++++++++--- .licenseignore | 2 + .../JExtractJNISampleApp/gradle.properties | 1 + Samples/JavaDependencySampleApp/Package.swift | 3 + .../JavaDependencySampleApp/gradle.properties | 1 + Samples/JavaKitSampleApp/Package.swift | 9 +- Samples/JavaKitSampleApp/gradle.properties | 1 + Samples/JavaProbablyPrime/Package.swift | 5 +- Samples/JavaProbablyPrime/gradle.properties | 1 + Samples/JavaSieve/Package.swift | 5 +- .../SwiftAndJavaJarSampleLib/Package.swift | 5 +- .../SwiftAndJavaJarSampleLib/ci-validate.sh | 27 +++-- .../gradle.properties | 1 + Samples/SwiftKitSampleApp/Package.swift | 5 +- Samples/SwiftKitSampleApp/gradle.properties | 1 + Samples/gradle.properties | 1 + docker/Dockerfile | 2 +- docker/install_jdk.sh | 6 +- gradle.properties | 1 + 22 files changed, 266 insertions(+), 61 deletions(-) create mode 100755 .github/scripts/install_swiftly.sh create mode 120000 Samples/JExtractJNISampleApp/gradle.properties create mode 120000 Samples/JavaDependencySampleApp/gradle.properties create mode 120000 Samples/JavaKitSampleApp/gradle.properties create mode 120000 Samples/JavaProbablyPrime/gradle.properties create mode 120000 Samples/SwiftAndJavaJarSampleLib/gradle.properties create mode 120000 Samples/SwiftKitSampleApp/gradle.properties create mode 120000 Samples/gradle.properties create mode 100644 gradle.properties diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index dfd0a435..33fa1f76 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -4,36 +4,32 @@ description: 'Prepare the CI environment by installing Swift and selected JDK et runs: using: composite steps: - - name: Install System Dependencies - run: apt-get -qq update && apt-get -qq install -y make curl wget libjemalloc2 libjemalloc-dev - shell: bash - - name: Cache JDKs - id: cache-jdk - uses: actions/cache@v4 - continue-on-error: true + - name: Set up JDK ${{ matrix.jdk_version }} + uses: actions/setup-java@v4 with: - path: /usr/lib/jvm/ - key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/*') }} - restore-keys: | - ${{ runner.os }}-jdk- - - name: Install JDK - if: steps.cache-jdk.outputs.cache-hit != 'true' - run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" + distribution: ${{ matrix.jdk_vendor }} + java-version: | + 24 + 21 + cache: 'gradle' + - name: Set JAVA_HOME_{N} shell: bash - # TODO: not using setup-java since incompatible with the swiftlang/swift base image - # - name: Install Untested Nightly Swift - # run: "bash -xc './docker/install_untested_nightly_swift.sh'" - - name: Cache local Gradle repository - uses: actions/cache@v4 - continue-on-error: true - with: - path: | - /root/.gradle/caches - /root/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('*/*.gradle*', 'settings.gradle') }} - restore-keys: | - ${{ runner.os }}-gradle- + run: | + if [[ -n "$JAVA_HOME_21_X64" ]]; then + echo "JAVA_HOME_21=$JAVA_HOME_21_X64" >> $GITHUB_ENV + elif [[ -n "$JAVA_HOME_21_ARM64" ]]; then + echo "JAVA_HOME_21=$JAVA_HOME_21_ARM64" >> $GITHUB_ENV + fi + if [[ -n "$JAVA_HOME_24_X64" ]]; then + echo "JAVA_HOME_24=$JAVA_HOME_24_X64" >> $GITHUB_ENV + elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then + echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV + fi + - name: Check Java environment + shell: bash + run: ./gradlew -q javaToolchains - name: Cache local SwiftPM repository + if: matrix.os_version == 'jammy' uses: actions/cache@v4 continue-on-error: true with: diff --git a/.github/scripts/install_swiftly.sh b/.github/scripts/install_swiftly.sh new file mode 100755 index 00000000..78fa3f6b --- /dev/null +++ b/.github/scripts/install_swiftly.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# This script is reused from Swiftly itself, see: +# https://github.com/swiftlang/swiftly/blob/main/scripts/prep-gh-action.sh +# +# This script does a bit of extra preparation of the docker containers used to run the GitHub workflows +# that are specific to this project's needs when building/testing. Note that this script runs on +# every supported Linux distribution and macOS so it must adapt to the distribution that it is running. + +if [[ "$(uname -s)" == "Linux" ]]; then + # Install the basic utilities depending on the type of Linux distribution + apt-get --help && apt-get update && TZ=Etc/UTC apt-get -y install curl make gpg tzdata + yum --help && (curl --help && yum -y install curl) && yum install make gpg +fi + +set -e + +while [ $# -ne 0 ]; do + arg="$1" + case "$arg" in + snapshot) + swiftMainSnapshot=true + ;; + *) + ;; + esac + shift +done + +echo "Installing swiftly" + +if [[ "$(uname -s)" == "Linux" ]]; then + curl -O "https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz" && tar zxf swiftly-*.tar.gz && ./swiftly init -y --skip-install + # shellcheck disable=SC1091 + . "/root/.local/share/swiftly/env.sh" +else + # shellcheck disable=SC2155 + export SWIFTLY_HOME_DIR="$(pwd)/swiftly-bootstrap" + export SWIFTLY_BIN_DIR="$SWIFTLY_HOME_DIR/bin" + export SWIFTLY_TOOLCHAINS_DIR="$SWIFTLY_HOME_DIR/toolchains" + + curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && pkgutil --check-signature swiftly.pkg && pkgutil --verbose --expand swiftly.pkg "${SWIFTLY_HOME_DIR}" && tar -C "${SWIFTLY_HOME_DIR}" -xvf "${SWIFTLY_HOME_DIR}"/swiftly-*/Payload && "$SWIFTLY_HOME_DIR/bin/swiftly" init -y --skip-install + + # shellcheck disable=SC1091 + . "$SWIFTLY_HOME_DIR/env.sh" +fi + +hash -r + +if [ -n "$GITHUB_ENV" ]; then + echo "Updating GitHub environment" + echo "PATH=$PATH" >> "$GITHUB_ENV" && echo "SWIFTLY_HOME_DIR=$SWIFTLY_HOME_DIR" >> "$GITHUB_ENV" && echo "SWIFTLY_BIN_DIR=$SWIFTLY_BIN_DIR" >> "$GITHUB_ENV" && echo "SWIFTLY_TOOLCHAINS_DIR=$SWIFTLY_TOOLCHAINS_DIR" >> "$GITHUB_ENV" +fi + +selector=() +runSelector=() + +if [ "$swiftMainSnapshot" == true ]; then + echo "Installing latest main-snapshot toolchain" + selector=("main-snapshot") + runSelector=("+main-snapshot") +elif [ -n "${SWIFT_VERSION}" ]; then + echo "Installing selected swift toolchain from SWIFT_VERSION environment variable" + selector=("${SWIFT_VERSION}") + runSelector=() +elif [ -f .swift-version ]; then + echo "Installing selected swift toolchain from .swift-version file" + selector=() + runSelector=() +else + echo "Installing latest toolchain" + selector=("latest") + runSelector=("+latest") +fi + +swiftly install --post-install-file=post-install.sh "${selector[@]}" + +if [ -f post-install.sh ]; then + echo "Performing swift toolchain post-installation" + chmod u+x post-install.sh && ./post-install.sh +fi + +echo "Displaying swift version" +swiftly run "${runSelector[@]}" swift --version + +if [[ "$(uname -s)" == "Linux" ]]; then + CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh" +fi diff --git a/.github/scripts/validate_sample.sh b/.github/scripts/validate_sample.sh index b5780976..64265b01 100755 --- a/.github/scripts/validate_sample.sh +++ b/.github/scripts/validate_sample.sh @@ -11,6 +11,8 @@ declare -r RESET='\033[0m' declare -r sampleDir="$1" declare -r CI_VALIDATE_SCRIPT='ci-validate.sh' +echo "Using Swift: $(which swift)" + echo "" echo "" echo "========================================================================" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c9d197e7..b55e0489 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -15,18 +15,37 @@ jobs: license_header_check_project_name: Swift.org test-java: - name: Java tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: true matrix: swift_version: ['6.1.2'] os_version: ['jammy'] - jdk_vendor: ['Corretto'] + 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" + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Gradle :SwiftKit:build + run: ./gradlew build -x test + - name: Gradle :SwiftKit:check + run: ./gradlew :SwiftKit:check --info + + test-java-macos: + name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: [self-hosted, macos, sequoia, ARM64] + strategy: + fail-fast: true + matrix: + swift_version: ['6.1.2'] + os_version: ['macos'] + jdk_vendor: ['corretto'] + env: SWIFT_JAVA_VERBOSE: true steps: - uses: actions/checkout@v4 @@ -36,22 +55,59 @@ jobs: run: ./gradlew build -x test - name: Gradle :SwiftKit:check run: ./gradlew :SwiftKit:check --debug + + benchmark-java: + name: Benchmark (JMH) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + swift_version: ['6.1.2'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env - name: Gradle compile JMH benchmarks run: ./gradlew compileJmh --info test-swift: - name: Swift tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: swift_version: ['6.1.2'] os_version: ['jammy'] - jdk_vendor: ['Corretto'] + 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" + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Swift Build + run: "swift build --build-tests --disable-sandbox" + - name: Swift Test + run: "swift test" + + test-swift-macos: + name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: [self-hosted, macos, sequoia, ARM64] + strategy: + fail-fast: false + matrix: + swift_version: ['6.1.2'] + os_version: ['macos'] + jdk_vendor: ['corretto'] + env: SWIFT_JAVA_VERBOSE: true steps: - uses: actions/checkout@v4 @@ -63,30 +119,58 @@ jobs: run: "swift test" verify-samples: - name: Verify Sample ${{ matrix.sample_app }} (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + name: Sample ${{ matrix.sample_app }} (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: swift_version: ['6.1.2'] os_version: ['jammy'] - jdk_vendor: ['Corretto'] - sample_app: [ + jdk_vendor: ['corretto'] + sample_app: [ # TODO: use a reusable-workflow to generate those names 'JavaDependencySampleApp', 'JavaKitSampleApp', 'JavaProbablyPrime', 'JavaSieve', 'SwiftAndJavaJarSampleLib', 'SwiftKitSampleApp', - 'JExtractJNISampleApp' + 'JExtractJNISampleApp', ] 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" + - name: "Verify sample: ${{ matrix.sample_app }}" + run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }} + + + verify-samples-macos: + name: Sample ${{ matrix.sample_app }} (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: [self-hosted, macos, sequoia, ARM64] + strategy: + fail-fast: false + matrix: + swift_version: ['6.1.2'] + os_version: ['macos'] + jdk_vendor: ['corretto'] + sample_app: [ # TODO: use a reusable-workflow to generate those names + 'JavaDependencySampleApp', + 'JavaKitSampleApp', + 'JavaProbablyPrime', + 'JavaSieve', + 'SwiftAndJavaJarSampleLib', + 'SwiftKitSampleApp', + 'JExtractJNISampleApp', + ] + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install Swiftly # we specifically install Swiftly in macOS jobs because we want a simpler way to find the right dylib paths for libraries + run: ./.github/scripts/install_swiftly.sh + env: + SWIFT_VERSION: "${{ matrix.swift_version }}" + - name: "Verify sample ${{ matrix.sample_app }}" run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }} diff --git a/.licenseignore b/.licenseignore index 003cac25..a49ab625 100644 --- a/.licenseignore +++ b/.licenseignore @@ -35,6 +35,8 @@ Makefile **/CMakeLists.txt **/*.jar **/generated/*.java +gradle.properties +**/gradle.properties **/generated/*.swift gradle/wrapper/gradle-wrapper.properties gradlew diff --git a/Samples/JExtractJNISampleApp/gradle.properties b/Samples/JExtractJNISampleApp/gradle.properties new file mode 120000 index 00000000..7677fb73 --- /dev/null +++ b/Samples/JExtractJNISampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index 4fa33116..506b8c93 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -44,6 +44,9 @@ let package = Package( name: "JavaDependencySampleApp", platforms: [ .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ diff --git a/Samples/JavaDependencySampleApp/gradle.properties b/Samples/JavaDependencySampleApp/gradle.properties new file mode 120000 index 00000000..7677fb73 --- /dev/null +++ b/Samples/JavaDependencySampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 0956290c..1b545819 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -43,11 +43,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "JavaKitSampleApp", platforms: [ - .macOS(.v13), - .iOS(.v13), - .tvOS(.v13), - .watchOS(.v6), - .macCatalyst(.v13), + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ diff --git a/Samples/JavaKitSampleApp/gradle.properties b/Samples/JavaKitSampleApp/gradle.properties new file mode 120000 index 00000000..7677fb73 --- /dev/null +++ b/Samples/JavaKitSampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaProbablyPrime/Package.swift b/Samples/JavaProbablyPrime/Package.swift index 4cc887f8..9f0ecff2 100644 --- a/Samples/JavaProbablyPrime/Package.swift +++ b/Samples/JavaProbablyPrime/Package.swift @@ -7,7 +7,10 @@ import PackageDescription let package = Package( name: "JavaProbablyPrime", platforms: [ - .macOS(.v10_15), + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ diff --git a/Samples/JavaProbablyPrime/gradle.properties b/Samples/JavaProbablyPrime/gradle.properties new file mode 120000 index 00000000..7677fb73 --- /dev/null +++ b/Samples/JavaProbablyPrime/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift index 65c10481..ee61a021 100644 --- a/Samples/JavaSieve/Package.swift +++ b/Samples/JavaSieve/Package.swift @@ -42,7 +42,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "JavaSieve", platforms: [ - .macOS(.v10_15), + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], dependencies: [ .package(name: "swift-java", path: "../../"), diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index c4c604ee..c350a0e7 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -43,7 +43,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "SwiftAndJavaJarSampleLib", platforms: [ - .macOS(.v15) + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ .library( diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh index 1b4769e8..f56b611e 100755 --- a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -7,13 +7,21 @@ set -x 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' +# This is how env variables are set by setup-java +if [ "$(uname -m)" = 'arm64' ]; then + ARCH=ARM64 + JAVAC="${JAVA_HOME_24_ARM64}/bin/javac" + JAVA="${JAVA_HOME_24_ARM64}/bin/java" +else + ARCH=X64 + JAVAC="${JAVA_HOME_24_X64}/bin/javac" + JAVA="${JAVA_HOME_24_X64}/bin/java" +fi + +if [ -n "$JAVA_HOME_24_$ARCH" ]; then + export JAVA_HOME="$JAVA_HOME_24_$ARCH" 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 @@ -24,8 +32,9 @@ MYLIB_CLASSPATH="$(pwd)/build/libs/*" CLASSPATH="$(pwd)/:${SWIFTKIT_CLASSPATH}:${MYLIB_CLASSPATH}" echo "CLASSPATH = ${CLASSPATH}" -javac -cp "${CLASSPATH}" Example.java +$JAVAC -cp "${CLASSPATH}" Example.java +# FIXME: move all this into Gradle or SwiftPM and make it easier to get the right classpath for running if [ "$(uname -s)" = 'Linux' ] then SWIFT_LIB_PATHS=/usr/lib/swift/linux @@ -35,6 +44,7 @@ then 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")" + ls "$SWIFT_LIB_PATHS" 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) @@ -44,13 +54,14 @@ then 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=$(find "$(swiftly use --print-location)" | grep dylib$ | grep libswiftCore | grep macos | head -n1 | 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 \ +${JAVA} --enable-native-access=ALL-UNNAMED \ -Djava.library.path="${SWIFT_LIB_PATHS}" \ -cp "${CLASSPATH}" \ Example diff --git a/Samples/SwiftAndJavaJarSampleLib/gradle.properties b/Samples/SwiftAndJavaJarSampleLib/gradle.properties new file mode 120000 index 00000000..7677fb73 --- /dev/null +++ b/Samples/SwiftAndJavaJarSampleLib/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftKitSampleApp/Package.swift index 34d8fcd3..8501062b 100644 --- a/Samples/SwiftKitSampleApp/Package.swift +++ b/Samples/SwiftKitSampleApp/Package.swift @@ -43,7 +43,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "SwiftKitSampleApp", platforms: [ - .macOS(.v15) + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ .library( diff --git a/Samples/SwiftKitSampleApp/gradle.properties b/Samples/SwiftKitSampleApp/gradle.properties new file mode 120000 index 00000000..7677fb73 --- /dev/null +++ b/Samples/SwiftKitSampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/gradle.properties b/Samples/gradle.properties new file mode 120000 index 00000000..7677fb73 --- /dev/null +++ b/Samples/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 6604d091..06b17a87 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,6 +21,6 @@ ENV LANGUAGE=en_US.UTF-8 COPY install_jdk.sh . # JDK dependency -RUN bash -xc 'JDK_VENDOR=Corretto ./install_jdk.sh' +RUN bash -xc 'JDK_VENDOR=corretto ./install_jdk.sh' ENV JAVA_HOME="/usr/lib/jvm/default-jdk" ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8d0ddda3..122c0d06 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -17,9 +17,9 @@ set -euo pipefail # We need JDK 24 because that's the supported version with latest FFM # However, we also need JDK 23 at most because Gradle does not support 24. -# Supported JDKs: Corretto +# Supported JDKs: corretto if [ "$JDK_VENDOR" = "" ]; then -declare -r JDK_VENDOR="Corretto" +declare -r JDK_VENDOR="corretto" fi apt-get update && apt-get install -y wget tree @@ -33,7 +33,7 @@ download_and_install_jdk() { echo "Installing $JDK_VENDOR JDK (${jdk_version})..." - if [ "$JDK_VENDOR" = 'Corretto' ]; then + if [ "$JDK_VENDOR" = 'corretto' ]; then if [ "$(uname -m)" = 'aarch64' ]; then case "$jdk_version" in "21") diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..335b6f40 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64,JAVA_HOME_21,JAVA_HOME_21_X64,JAVA_HOME_21_ARM64 \ No newline at end of file From 3c3e41185c79266bf671ee8ff4ee3797a6c1c053 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Jul 2025 18:54:23 +0900 Subject: [PATCH 084/178] wrap-java: add option to match java package style directory nesting (#296) --- Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 349144ab..05ac7651 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -52,6 +52,9 @@ extension SwiftJava { @Option(help: "Cache directory for intermediate results and other outputs between runs") var cacheDirectory: String? + @Option(help: "Match java package directory structure with generated Swift files") + var swiftMatchPackageDirectoryStructure: Bool = false + @Argument(help: "Path to .jar file whose Java classes should be wrapped using Swift bindings") var input: String } @@ -229,11 +232,16 @@ extension SwiftJava.WrapJavaCommand { """ + var generatedFileOutputDir = self.actualOutputDirectory + if self.swiftMatchPackageDirectoryStructure { + generatedFileOutputDir?.append(path: javaClass.getPackageName().replacing(".", with: "/")) + } + let swiftFileName = try! translator.getSwiftTypeName(javaClass, preferValueTypes: false) .swiftName.replacing(".", with: "+") + ".swift" try writeContents( swiftFileText, - outputDirectory: self.actualOutputDirectory, + outputDirectory: generatedFileOutputDir, to: swiftFileName, description: "Java class '\(javaClass.getName())' translation" ) From 56dfcfa0a054fee24098bf54eea041dfe2d21729 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 3 Jul 2025 12:24:45 +0900 Subject: [PATCH 085/178] rename package to fit ecosystem pattern of lowercase and dash (#297) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 58ee1840..403617da 100644 --- a/Package.swift +++ b/Package.swift @@ -84,7 +84,7 @@ let javaIncludePath = "\(javaHome)/include" #endif let package = Package( - name: "SwiftJava", + name: "swift-java", platforms: [ .macOS(.v15) ], From e887a9fdb35f66cf0cfcec38b0d50d5aaa4cda70 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 3 Jul 2025 13:10:37 +0200 Subject: [PATCH 086/178] [JExtract] Generate JNI code for classes (static methods and initializers) (#298) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 37 + Samples/JExtractJNISampleApp/build.gradle | 5 + Samples/JExtractJNISampleApp/ci-validate.sh | 3 +- .../com/example/swift/HelloJava2SwiftJNI.java | 5 + .../com/example/swift/MySwiftClassTest.java | 37 + .../com/example/swift/MySwiftLibraryTest.java | 57 ++ .../Convenience/JavaType+Extensions.swift | 38 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 878 +++++++++--------- Sources/JExtractSwiftLib/ImportedDecls.swift | 18 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 197 ++++ ...ISwift2JavaGenerator+JavaTranslation.swift | 123 +++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 268 ++++++ .../JNI/JNISwift2JavaGenerator.swift | 262 +----- Sources/JExtractSwiftLib/JavaParameter.swift | 25 + Sources/JavaTypes/JavaType+SwiftNames.swift | 7 + .../JNI/JNIClassTests.swift | 173 ++++ .../JNI/JNIModuleTests.swift | 30 +- 18 files changed, 1444 insertions(+), 721 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java create mode 100644 Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift create mode 100644 Sources/JExtractSwiftLib/JavaParameter.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIClassTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift new file mode 100644 index 00000000..fca6236f --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public class MySwiftClass { + let x: Int64 + let y: Int64 + + public static func method() { + p("Hello from static method in a class!") + } + + public init(x: Int64, y: Int64) { + self.x = x + self.y = y + p("\(self)") + } + + public init() { + self.x = 10 + self.y = 5 + } + + deinit { + p("deinit called!") + } +} diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/JExtractJNISampleApp/build.gradle index d0e32857..03794ff7 100644 --- a/Samples/JExtractJNISampleApp/build.gradle +++ b/Samples/JExtractJNISampleApp/build.gradle @@ -156,6 +156,11 @@ dependencies { tasks.named('test', Test) { useJUnitPlatform() + + testLogging { + events "failed" + exceptionFormat "full" + } } application { diff --git a/Samples/JExtractJNISampleApp/ci-validate.sh b/Samples/JExtractJNISampleApp/ci-validate.sh index 07b42627..c7a68d22 100755 --- a/Samples/JExtractJNISampleApp/ci-validate.sh +++ b/Samples/JExtractJNISampleApp/ci-validate.sh @@ -3,4 +3,5 @@ set -x set -e -./gradlew run \ No newline at end of file +./gradlew run +./gradlew test \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 36bad93c..7d0d77e1 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -40,6 +40,11 @@ static void examples() { long i = MySwiftLibrary.globalMakeInt(); SwiftKit.trace("globalMakeInt() = " + i); + MySwiftClass.method(); + + MySwiftClass myClass = MySwiftClass.init(10, 5); + MySwiftClass myClass2 = MySwiftClass.init(); + System.out.println("DONE."); } } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java new file mode 100644 index 00000000..994240cb --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftClassTest { + @Test + void init_noParameters() { + MySwiftClass c = MySwiftClass.init(); + assertNotNull(c); + } + + @Test + void init_withParameters() { + MySwiftClass c = MySwiftClass.init(1337, 42); + assertNotNull(c); + } + +} \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java new file mode 100644 index 00000000..f1cefd19 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftLibraryTest { + @Test + void call_helloWorld() { + MySwiftLibrary.helloWorld(); + } + + @Test + void call_globalTakeInt() { + MySwiftLibrary.globalTakeInt(12); + } + + @Test + void call_globalMakeInt() { + long i = MySwiftLibrary.globalMakeInt(); + assertEquals(42, i); + } + + @Test + void call_globalTakeIntInt() { + MySwiftLibrary.globalTakeIntInt(1337, 42); + } + + @Test + void call_writeString_jextract() { + var string = "Hello Swift!"; + long reply = MySwiftLibrary.globalWriteString(string); + + assertEquals(string.length(), reply); + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift new file mode 100644 index 00000000..9da3ae5b --- /dev/null +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +extension JavaType { + var jniTypeSignature: String { + switch self { + case .boolean: "Z" + case .byte: "B" + case .char: "C" + case .short: "S" + case .int: "I" + case .long: "J" + case .float: "F" + case .double: "D" + case .class(let package, let name): + if let package { + "L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);" + } else { + "L\(name);" + } + case .array(let javaType): "[\(javaType.jniTypeSignature)" + case .void: fatalError("There is no type signature for 'void'") + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index a6cc6b26..364fd270 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -439,7 +439,7 @@ extension FFMSwift2JavaGenerator { } } -extension JavaConversionStep { +extension FFMSwift2JavaGenerator.JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index af1ddd09..16efddea 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -34,538 +34,526 @@ extension FFMSwift2JavaGenerator { translatedDecls[decl] = translated 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] -/// 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 + } - /// 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 + } -/// 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) + /// Translated Java API representing a Swift API. /// - /// 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 -} + /// 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 TranslatedFunctionDecl { + /// Java function name. + let name: String + /// Functional interfaces required for the Java method. + let functionTypes: [TranslatedFunctionType] -/// 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 TranslatedFunctionDecl { - /// Java function name. - let name: String + /// Function signature. + let translatedSignature: TranslatedFunctionSignature - /// 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 - var swiftType: SwiftFunctionType - var cdeclType: SwiftFunctionType - - /// Whether or not this functional interface with C ABI compatible. - var isCompatibleWithC: Bool { - result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder) + /// Cdecl lowerd signature. + let loweredSignature: LoweredFunctionSignature } -} -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 + /// Function signature for a Java API. + struct TranslatedFunctionSignature { + var selfParameter: TranslatedParameter? + var parameters: [TranslatedParameter] + var result: TranslatedResult } - /// 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 + /// 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 + var swiftType: SwiftFunctionType + var cdeclType: SwiftFunctionType + + /// Whether or not this functional interface with C ABI compatible. + var isCompatibleWithC: Bool { + result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder) + } } -} -struct JavaTranslation { - var swiftStdlibTypes: SwiftStandardLibraryTypeDecls + struct JavaTranslation { + var swiftStdlibTypes: SwiftStandardLibraryTypeDecls - func translate( - _ decl: ImportedFunc - ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) - let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) + 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 - } + // Name. + let javaName = switch decl.apiKind { + case .getter: "get\(decl.name.toCamelCase)" + case .setter: "set\(decl.name.toCamelCase)" + 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)" - guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else { - preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)") + // 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)" + 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 + break + default: + break } - let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy) - funcTypes.append(translatedClosure) - case .tuple: - // TODO: Implement - break - default: - break } - } - return TranslatedFunctionDecl( - name: javaName, - functionTypes: funcTypes, - translatedSignature: translatedSignature, - loweredSignature: loweredSignature - ) - } - - /// Translate Swift closure type to Java functional interface. - func translateFunctionType( - name: String, - swiftType: SwiftFunctionType, - cdeclType: SwiftFunctionType - ) throws -> TranslatedFunctionType { - var translatedParams: [TranslatedParameter] = [] - - for (i, param) in swiftType.parameters.enumerated() { - let paramName = param.parameterName ?? "_\(i)" - translatedParams.append( - try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + return TranslatedFunctionDecl( + name: javaName, + functionTypes: funcTypes, + translatedSignature: translatedSignature, + loweredSignature: loweredSignature ) } - guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { - throw JavaTranslationError.unhandledType(.function(swiftType)) - } + /// Translate Swift closure type to Java functional interface. + func translateFunctionType( + name: String, + swiftType: SwiftFunctionType, + cdeclType: SwiftFunctionType + ) throws -> TranslatedFunctionType { + var translatedParams: [TranslatedParameter] = [] + + for (i, param) in swiftType.parameters.enumerated() { + let paramName = param.parameterName ?? "_\(i)" + translatedParams.append( + try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + ) + } - let transltedResult = TranslatedResult( - javaResultType: resultCType.javaType, - outParameters: [], - conversion: .placeholder - ) - - return TranslatedFunctionType( - name: name, - parameters: translatedParams, - result: transltedResult, - swiftType: swiftType, - cdeclType: cdeclType - ) - } + guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { + throw JavaTranslationError.unhandledType(.function(swiftType)) + } - 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) - ], + let transltedResult = TranslatedResult( + javaResultType: resultCType.javaType, + outParameters: [], conversion: .placeholder ) + + return TranslatedFunctionType( + name: name, + parameters: translatedParams, + result: transltedResult, + swiftType: swiftType, + cdeclType: cdeclType + ) } - 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") + func translateClosureParameter( + _ type: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> TranslatedParameter { + if let cType = try? CType(cdeclType: type) { + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: cType.javaType) + ], + conversion: .placeholder + ) + } + + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: .javaForeignMemorySegment) ], - withArena: false + conversion: .method( + .explodedName(component: "pointer"), + methodName: "reinterpret", + arguments: [ + .explodedName(component: "count") + ], + withArena: false + ) ) - ) - default: - break + default: + break + } } + default: + break } - default: - break + throw JavaTranslationError.unhandledType(type) } - 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 - /// low-level FFM down-calling interface. - func translate( - loweredFunctionSignature: LoweredFunctionSignature, - methodName: String - ) 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!, - methodName: methodName, - 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, + /// 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, + methodName: String + ) 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!, methodName: methodName, - parameterName: parameterName + parameterName: swiftSelf.parameterName ?? "self" ) + } else { + selfParameter = nil } - // Result. - let result = try self.translate( - swiftResult: swiftSignature.result, - loweredResult: loweredFunctionSignature.result - ) - - return TranslatedFunctionSignature( - selfParameter: selfParameter, - parameters: parameters, - result: result - ) - } - - /// 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 - - // 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: parameterName + // 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, + methodName: methodName, + parameterName: parameterName ) - ], - conversion: .placeholder + } + + // Result. + let result = try self.translate( + swiftResult: swiftSignature.result, + loweredResult: loweredFunctionSignature.result ) - } - 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: parameterName) - ], - conversion: .swiftValueSelfSegment(.placeholder) + return TranslatedFunctionSignature( + selfParameter: selfParameter, + parameters: parameters, + result: result ) + } - 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) + /// 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 + + // 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( + name: parameterName, type: javaType + ) + ], + conversion: .placeholder + ) + } - case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: - return TranslatedParameter( - javaParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: parameterName), - ], - conversion: .commaSeparated([ - .placeholder, - .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) - ]) - ) + switch swiftType { + case .metatype: + // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType")) + ], + conversion: .swiftValueSelfSegment(.placeholder) + ) - case .string: - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: .javaLangString, - name: parameterName - ) - ], - conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) - ) + 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 .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: .javaForeignMemorySegment), + ], + conversion: .commaSeparated([ + .placeholder, + .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) + ]) + ) - default: + case .string: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: .javaLangString + ) + ], + conversion: .call(.placeholder, 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) } - } - // Generic types are not supported yet. - guard swiftNominalType.genericArguments == nil else { - throw JavaTranslationError.unhandledType(swiftType) - } + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: try translate(swiftType: swiftType) + ) + ], + conversion: .swiftValueSelfSegment(.placeholder) + ) - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: try translate(swiftType: swiftType), - name: parameterName - ) - ], - conversion: .swiftValueSelfSegment(.placeholder) - ) + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) - case .tuple: - // TODO: Implement. - throw JavaTranslationError.unhandledType(swiftType) - - case .function: - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"), - name: parameterName) - ], - conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) - ) + case .function: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)")) + ], + conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) + ) - case .optional: - throw JavaTranslationError.unhandledType(swiftType) + case .optional: + throw JavaTranslationError.unhandledType(swiftType) + } } - } - /// Translate a Swift API result to the user-facing Java API result. - 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: .placeholder - ) - } + /// Translate a Swift API result to the user-facing Java API result. + 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: .placeholder + ) + } - 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(.placeholder, 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(.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( - .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), - methodName: "reinterpret", - arguments: [ - .readMemorySegment(.explodedName(component: "count"), as: .long), + case .nominal(let swiftNominalType): + if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedResult( + javaResultType: .javaForeignMemorySegment, + outParameters: [ + JavaParameter(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), ], - withArena: false + conversion: .method( + .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), + methodName: "reinterpret", + arguments: [ + .readMemorySegment(.explodedName(component: "count"), as: .long), + ], + withArena: false + ) ) - ) - 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: + 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) } - } - // 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(name: "", type: javaType) + ], + conversion: .constructSwiftValue(.placeholder, javaType) + ) - let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) - return TranslatedResult( - javaResultType: javaType, - outParameters: [ - JavaParameter(type: javaType, name: "") - ], - conversion: .constructSwiftValue(.placeholder, javaType) - ) + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) - case .tuple: - // TODO: Implement. - throw JavaTranslationError.unhandledType(swiftType) + case .optional, .function: + 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) + func translate( + swiftType: SwiftType + ) throws -> JavaType { + guard let nominalName = swiftType.asNominalTypeDeclaration?.name else { + throw JavaTranslationError.unhandledType(swiftType) + } + return .class(package: nil, name: nominalName) } - return .class(package: nil, name: nominalName) } -} -/// Describes how to convert values between Java types and FFM types. -enum JavaConversionStep { - // The input - case placeholder + /// Describes how to convert values between Java types and FFM types. + enum JavaConversionStep { + // The input + case placeholder - // The input exploded into components. - case explodedName(component: String) + // The input exploded into components. + case explodedName(component: String) - // A fixed value - case constant(String) + // A fixed value + case constant(String) - // 'value.$memorySegment()' - indirect case swiftValueSelfSegment(JavaConversionStep) + // 'value.$memorySegment()' + indirect case swiftValueSelfSegment(JavaConversionStep) - // call specified function using the placeholder as arguments. - // If `withArena` is true, `arena$` argument is added. - indirect case call(JavaConversionStep, function: String, withArena: Bool) + // call specified function using the placeholder as arguments. + // If `withArena` is true, `arena$` argument is added. + indirect case call(JavaConversionStep, function: String, withArena: Bool) - // Apply a method on the placeholder. - // If `withArena` is true, `arena$` argument is added. - indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) + // Apply a method on the placeholder. + // If `withArena` is true, `arena$` argument is added. + indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) - // Call 'new \(Type)(\(placeholder), swiftArena$)'. - indirect case constructSwiftValue(JavaConversionStep, JavaType) + // Call 'new \(Type)(\(placeholder), swiftArena$)'. + indirect case constructSwiftValue(JavaConversionStep, JavaType) - // Construct the type using the placeholder as arguments. - indirect case construct(JavaConversionStep, JavaType) + // Construct the type using the placeholder as arguments. + indirect case construct(JavaConversionStep, JavaType) - // Casting the placeholder to the certain type. - indirect case cast(JavaConversionStep, JavaType) + // Casting the placeholder to the certain type. + indirect case cast(JavaConversionStep, JavaType) - // Convert the results of the inner steps to a comma separated list. - indirect case commaSeparated([JavaConversionStep]) + // 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 readMemorySegment(JavaConversionStep, as: JavaType) + // Refer an exploded argument suffixed with `_\(name)`. + indirect case readMemorySegment(JavaConversionStep, as: JavaType) - var isPlaceholder: Bool { - return if case .placeholder = self { true } else { false } + var isPlaceholder: Bool { + return if case .placeholder = self { true } else { false } + } } } + +extension FFMSwift2JavaGenerator.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 + } +} + + extension CType { /// Map lowered C type to Java type for FFM binding. var javaType: JavaType { diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 0a5952f3..76d53d88 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -40,6 +40,10 @@ package class ImportedNominalType: ImportedDecl { var swiftType: SwiftType { return .nominal(.init(nominalTypeDecl: swiftNominal)) } + + var qualifiedName: String { + self.swiftNominal.qualifiedName + } } public final class ImportedFunc: ImportedDecl, CustomStringConvertible { @@ -74,6 +78,20 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } } + var isStatic: Bool { + if case .staticMethod = functionSignature.selfParameter { + return true + } + return false + } + + var isInitializer: Bool { + if case .initializer = functionSignature.selfParameter { + return true + } + return false + } + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift new file mode 100644 index 00000000..ac775638 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.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 +// +//===----------------------------------------------------------------------===// + +extension JNISwift2JavaGenerator { + func writeExportedJavaSources() throws { + var printer = CodePrinter() + try writeExportedJavaSources(&printer) + } + + 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" + logger.info("Printing contents: \(filename)") + printImportedNominal(&printer, ty) + + if let outputFile = try printer.writeContents( + outputDirectory: javaOutputDirectory, + javaPackagePath: javaPackagePath, + filename: filename + ) { + logger.info("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") + } + } + + 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))") + } + } + + private func printModule(_ printer: inout CodePrinter) { + printHeader(&printer) + printPackage(&printer) + + printModuleClass(&printer) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + static { + System.loadLibrary(LIB_NAME); + } + """ + ) + + for decl in analysis.importedGlobalFuncs { + self.logger.trace("Print global function: \(decl)") + printFunctionBinding(&printer, decl) + printer.println() + } + } + } + + private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printHeader(&printer) + printPackage(&printer) + + printNominal(&printer, decl) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + """ + ) + + printer.println() + + printer.print( + """ + private long selfPointer; + + private \(decl.swiftNominal.name)(long selfPointer) { + this.selfPointer = selfPointer; + } + """ + ) + + printer.println() + + for initializer in decl.initializers { + printInitializerBindings(&printer, initializer, type: decl) + } + + for method in decl.methods { + printFunctionBinding(&printer, method) + } + } + } + + 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 printNominal( + _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void + ) { + printer.printBraceBlock("public final class \(decl.swiftNominal.name)") { printer in + body(&printer) + } + } + + 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) { + printDeclDocumentation(&printer, decl) + printer.print( + "public static native \(renderFunctionSignature(decl));" + ) + } + + private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { + let translatedDecl = translatedDecl(for: decl) + + printDeclDocumentation(&printer, decl) + printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in + let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) + printer.print( + """ + long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); + return new \(type.qualifiedName)(selfPointer); + """ + ) + } + + let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + printer.print("private static native long allocatingInit(\(parameters.joined(separator: ", ")));") + } + + private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + printer.print( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + } + + /// Renders a Java function signature + /// + /// `func method(x: Int, y: Int) -> Int` becomes + /// `long method(long x, long y)` + private func renderFunctionSignature(_ decl: ImportedFunc) -> String { + let translatedDecl = translatedDecl(for: decl) + let resultType = translatedDecl.translatedFunctionSignature.resultType + let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + let throwsClause = decl.isThrowing ? " throws Exception" : "" + + return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift new file mode 100644 index 00000000..a5df702c --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -0,0 +1,123 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +extension JNISwift2JavaGenerator { + func translatedDecl( + for decl: ImportedFunc + ) -> TranslatedFunctionDecl { + if let cached = translatedDecls[decl] { + return cached + } + + let translation = JavaTranslation(swiftModuleName: self.swiftModuleName) + let translated = translation.translate(decl) + + translatedDecls[decl] = translated + return translated + } + + struct JavaTranslation { + let swiftModuleName: String + + func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { + let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + // Types with no parent will be outputted inside a "module" class. + let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName + + return TranslatedFunctionDecl( + name: decl.name, + parentName: parentName, + translatedFunctionSignature: translatedFunctionSignature + ) + } + + func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) -> TranslatedFunctionSignature { + let parameters = functionSignature.parameters.enumerated().map { idx, param in + let parameterName = param.parameterName ?? "arg\(idx))" + return translate(swiftParam: param, parameterName: parameterName) + } + + return TranslatedFunctionSignature( + parameters: parameters, + resultType: translate(swiftType: functionSignature.result.type) + ) + } + + func translate(swiftParam: SwiftParameter, parameterName: String) -> JavaParameter { + return JavaParameter( + name: parameterName, + type: translate(swiftType: swiftParam.type) + ) + } + + func translate(swiftType: SwiftType) -> JavaType { + switch swiftType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + guard let javaType = translate(standardLibraryType: knownType) else { + fatalError("unsupported known type: \(knownType)") + } + return javaType + } + + return .class(package: nil, name: nominalType.nominalTypeDecl.name) + + case .tuple([]): + return .void + + case .metatype, .optional, .tuple, .function: + fatalError("unsupported type: \(self)") + } + } + + func translate(standardLibraryType: SwiftStandardLibraryTypeKind) -> JavaType? { + switch standardLibraryType { + case .bool: .boolean + 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 .string: .javaLangString + case .int, .uint, .uint8, .uint32, .uint64, + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer: + nil + } + } + } + + struct TranslatedFunctionDecl { + /// Java function name + let name: String + + /// The name of the Java parent scope this function is declared in + let parentName: String + + /// Function signature + let translatedFunctionSignature: TranslatedFunctionSignature + } + + struct TranslatedFunctionSignature { + let parameters: [JavaParameter] + let resultType: JavaType + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift new file mode 100644 index 00000000..c61dd929 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -0,0 +1,268 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +extension JNISwift2JavaGenerator { + func writeSwiftThunkSources() throws { + var printer = CodePrinter() + try writeSwiftThunkSources(&printer) + } + + package func writeSwiftExpectedEmptySources() throws { + for expectedFileName in self.expectedOutputSwiftFiles { + logger.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" + + do { + logger.trace("Printing swift module class: \(moduleFilename)") + + try printGlobalSwiftThunkSources(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: moduleFilename + ) { + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(moduleFilename) + } + + 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" + logger.info("Printing contents: \(filename)") + + do { + try printNominalTypeThunks(&printer, ty) + + if let outputFile = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: filename) { + print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(filename) + } + } catch { + logger.warning("Failed to write to Swift thunks: \(filename)") + } + } + } catch { + logger.warning("Failed to write to Swift thunks: \(moduleFilename)") + } + } + + private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { + printHeader(&printer) + + for decl in analysis.importedGlobalFuncs { + printSwiftFunctionThunk(&printer, decl) + printer.println() + } + } + + private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { + printHeader(&printer) + + for initializer in type.initializers { + printInitializerThunk(&printer, initializer) + printer.println() + } + + for method in type.methods { + printSwiftFunctionThunk(&printer, method) + printer.println() + } + } + + private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = translatedDecl(for: decl) + let typeName = translatedDecl.parentName + + printCDecl( + &printer, + javaMethodName: "allocatingInit", + parentName: translatedDecl.parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: true, + resultType: .long + ) { printer in + let downcallArguments = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + // TODO: Throwing initializers + printer.print( + """ + let selfPointer = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) + selfPointer.initialize(to: \(typeName)(\(downcallArguments))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + """ + ) + } + } + + private func printSwiftFunctionThunk( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translatedDecl = self.translatedDecl(for: decl) + let parentName = translatedDecl.parentName ?? swiftModuleName + let swiftReturnType = decl.functionSignature.result.type + + printCDecl(&printer, decl) { printer in + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + let tryClause: String = decl.isThrowing ? "try " : "" + let functionDowncall = + "\(tryClause)\(parentName).\(decl.name)(\(downcallParameters))" + + 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( + """ + do { + \(innerBody) + } catch { + environment.throwAsException(error) + \(dummyReturn) + } + """ + ) + } else { + printer.print(innerBody) + } + } + } + + private func printCDecl( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + _ body: (inout CodePrinter) -> Void + ) { + let translatedDecl = translatedDecl(for: decl) + let parentName = translatedDecl.parentName + + printCDecl( + &printer, + javaMethodName: translatedDecl.name, + parentName: parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: decl.isStatic || decl.isInitializer || !decl.hasParent, + resultType: translatedDecl.translatedFunctionSignature.resultType, + body + ) + } + + private func printCDecl( + _ printer: inout CodePrinter, + javaMethodName: String, + parentName: String, + parameters: [JavaParameter], + isStatic: Bool, + resultType: JavaType, + _ body: (inout CodePrinter) -> Void + ) { + var jniSignature = parameters.reduce(into: "") { signature, parameter in + signature += parameter.type.jniTypeSignature + } + + // Escape signature characters + jniSignature = jniSignature + .replacingOccurrences(of: "_", with: "_1") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: ";", with: "_2") + .replacingOccurrences(of: "[", with: "_3") + + let cName = + "Java_" + + self.javaPackage.replacingOccurrences(of: ".", with: "_") + + "_\(parentName)_" + + javaMethodName + + "__" + + jniSignature + let translatedParameters = parameters.map { + "\($0.name): \($0.type.jniTypeName)" + } + let thisParameter = isStatic ? "thisClass: jclass" : "thisObject: jobject" + + let thunkParameters = + [ + "environment: UnsafeMutablePointer!", + thisParameter + ] + translatedParameters + let thunkReturnType = !resultType.isVoid ? " -> \(resultType.jniTypeName)" : "" + + // TODO: Think about function overloads + printer.printBraceBlock( + """ + @_cdecl("\(cName)") + func \(cName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) + """ + ) { printer in + body(&printer) + } + } + + private func printHeader(_ printer: inout CodePrinter) { + printer.print( + """ + // Generated by swift-java + + import JavaKit + + """ + ) + } + + /// Renders the arguments for making a downcall + private func renderDowncallArguments( + swiftFunctionSignature: SwiftFunctionSignature, + translatedFunctionSignature: TranslatedFunctionSignature + ) -> String { + zip( + swiftFunctionSignature.parameters, + translatedFunctionSignature.parameters + ).map { originalParam, translatedParam in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.name), in: environment!)" + } + .joined(separator: ", ") + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 0f16e697..b242cf66 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -28,6 +28,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var thunkNameRegistry = ThunkNameRegistry() + /// 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 @@ -71,262 +74,3 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { } } } - -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 writeSwiftExpectedEmptySources() throws { - for expectedFileName in self.expectedOutputSwiftFiles { - logger.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" - - do { - logger.trace("Printing swift module class: \(moduleFilename)") - - try printGlobalSwiftThunkSources(&printer) - - if let outputFile = try printer.writeContents( - outputDirectory: self.swiftOutputDirectory, - javaPackagePath: nil, - filename: moduleFilename - ) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") - self.expectedOutputSwiftFiles.remove(moduleFilename) - } - } 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.map { "\($0): " } ?? "" - return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" - } - let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = - "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" - - 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( - """ - do { - \(innerBody) - } catch { - environment.throwAsException(error) - \(dummyReturn) - } - """ - ) - } else { - printer.print(innerBody) - } - } - } -} - -extension JNISwift2JavaGenerator { - private func printModule(_ printer: inout CodePrinter) { - printHeader(&printer) - printPackage(&printer) - - printModuleClass(&printer) { printer in - printer.print( - """ - static final String LIB_NAME = "\(swiftModuleName)"; - - static { - System.loadLibrary(LIB_NAME); - } - """ - ) - - 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))")" - } - let throwsClause = decl.isThrowing ? " throws Exception" : "" - - printer.print( - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ - """ - ) - printer.print( - "public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);" - ) - } -} - -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 .string: .javaLangString - case .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: - nil - } - } -} diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift new file mode 100644 index 00000000..72896419 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaParameter.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 JavaTypes + +/// Represent a parameter in Java code. +struct JavaParameter { + let name: String + let type: JavaType + + var asParameter: String { + "\(type) \(name)" + } +} diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index d01398b8..492ff459 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -45,6 +45,13 @@ extension JavaType { } } + public var isVoid: Bool { + if case .void = self { + return true + } + return false + } + public var isString: Bool { switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift new file mode 100644 index 00000000..c39d5815 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIClassTests { + let source = """ + public class MyClass { + let x: Int64 + let y: Int64 + + public static func method() {} + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public init() { + self.x = 0 + self.y = 0 + } + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + public final class MyClass { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + private long selfPointer; + + private MyClass(long selfPointer) { + this.selfPointer = selfPointer; + } + """, + ]) + } + + @Test + func staticMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public static func method() + * } + */ + public static native void method(); + """ + ] + ) + } + + @Test + func staticMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass_method__") + func Java_com_example_swift_MyClass_method__(environment: UnsafeMutablePointer!, thisClass: jclass) { + MyClass.method() + } + """ + ] + ) + } + + @Test + func initializer_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ + public static MyClass init(long x, long y) { + long selfPointer = MyClass.allocatingInit(x, y); + return new MyClass(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init() + * } + */ + public static MyClass init() { + long selfPointer = MyClass.allocatingInit(); + return new MyClass(selfPointer); + } + """, + """ + private static native long allocatingInit(long x, long y); + """, + """ + private static native long allocatingInit(); + """ + ] + ) + } + + @Test + func initializer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass_allocatingInit__") + func Java_com_example_swift_MyClass_allocatingInit__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyClass()) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass_allocatingInit__JJ") + func Java_com_example_swift_MyClass_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index e483a4dd..d6a030a8 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -19,7 +19,7 @@ import Testing struct JNIModuleTests { let globalMethodWithPrimitives = """ public func helloWorld() - public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 public func otherPrimitives(b: Bool, f: Float, d: Double) """ @@ -73,7 +73,7 @@ struct JNIModuleTests { /** * Downcall to Swift: * {@snippet lang=swift : - * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ public static native char takeIntegers(byte i1, short i2, int i3, long i4); @@ -100,21 +100,21 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_helloWorld") - func swiftjava_SwiftModule_helloWorld(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule_helloWorld__") + func Java_com_example_swift_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!)) + @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers__BSIJ") + func Java_com_example_swift_SwiftModule_takeIntegers__BSIJ(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: Int64(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) { + @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives__ZFD") + func Java_com_example_swift_SwiftModule_otherPrimitives__ZFD(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!)) } """ @@ -151,8 +151,8 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_copy") - func swiftjava_SwiftModule_copy__(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { + @_cdecl("Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2") + func Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { let result = SwiftModule.copy(String(fromJNI: string, in: environment!)) return result.getJNIValue(in: environment) } @@ -199,8 +199,8 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_methodA") - func swiftjava_SwiftModule_methodA(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule_methodA__") + func Java_com_example_swift_SwiftModule_methodA__(environment: UnsafeMutablePointer!, thisClass: jclass) { do { try SwiftModule.methodA() } catch { @@ -209,8 +209,8 @@ struct JNIModuleTests { } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_methodB") - func swiftjava_SwiftModule_methodB(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + @_cdecl("Java_com_example_swift_SwiftModule_methodB__") + func Java_com_example_swift_SwiftModule_methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { do { let result = try SwiftModule.methodB() return result.getJNIValue(in: environment) From d266a4452a115f58d0d73f2e89cd921a8f611aab Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 8 Jul 2025 16:03:36 +0200 Subject: [PATCH 087/178] Separate SwiftKit into SwiftKitCore and SwiftKitFFM (#300) --- .github/actions/prepare_env/action.yml | 1 + .github/workflows/pull_request.yml | 24 +++-- ...d-logic.java-common-conventions.gradle.kts | 15 ---- Samples/JExtractJNISampleApp/build.gradle | 2 +- .../com/example/swift/HelloJava2SwiftJNI.java | 6 +- Samples/SwiftAndJavaJarSampleLib/build.gradle | 3 +- .../SwiftAndJavaJarSampleLib/ci-validate.sh | 5 +- .../{ => ffm}/JavaToSwiftBenchmark.java | 2 +- .../com/example/swift/HelloJava2Swift.java | 19 ++-- .../com/example/swift/MySwiftClassTest.java | 12 +-- .../com/example/swift/MySwiftLibraryTest.java | 4 - .../swift/swiftkit/ffm}/MySwiftClassTest.java | 14 +-- .../swiftkit/{ => ffm}/SwiftArenaTest.java | 15 ++-- Samples/SwiftKitSampleApp/build.gradle | 4 +- .../{ => ffm}/JavaToSwiftBenchmark.java | 6 +- .../{ => ffm}/StringPassingBenchmark.java | 9 +- .../com/example/swift/HelloJava2Swift.java | 33 +++---- .../com/example/swift/MySwiftClassTest.java | 12 +-- .../com/example/swift/MySwiftLibraryTest.java | 5 -- .../swift/swiftkitffm}/MySwiftClassTest.java | 16 ++-- .../MySwiftStructTest.java | 5 +- .../SwiftArenaTest.java | 23 +++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 10 +-- ...MSwift2JavaGenerator+JavaTranslation.swift | 6 +- .../FFM/FFMSwift2JavaGenerator.swift | 21 ++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JExtractSwiftLib/SwiftKit+Printing.swift | 2 +- {SwiftKit => SwiftKitCore}/build.gradle | 8 +- .../swiftkit/core}/ClosableSwiftArena.java | 3 +- .../core}/ConfinedSwiftMemorySession.java | 29 ++----- .../swift/swiftkit/core/JNISwiftInstance.java | 56 ++++++++++++ .../core/JNISwiftInstanceCleanup.java | 31 +++++++ .../org/swift/swiftkit/core}/SwiftArena.java | 29 +------ .../swift/swiftkit/core}/SwiftInstance.java | 43 +++------ .../swiftkit/core/SwiftInstanceCleanup.java | 20 +++++ .../swift/swiftkit/core/SwiftLibraries.java | 83 ++++++++++++++++++ .../org/swift/swiftkit/core}/SwiftValue.java | 2 +- .../swiftkit/core/WrongThreadException.java | 14 ++- .../swiftkit/core}/util/PlatformUtils.java | 2 +- .../swiftkit/core}/util/StringUtils.java | 2 +- SwiftKitFFM/build.gradle | 87 +++++++++++++++++++ .../ffm/AllocatingAutoSwiftMemorySession.java | 22 ++--- .../swiftkit/ffm/AllocatingSwiftArena.java | 34 ++++++++ .../ffm/ClosableAllocatingSwiftArena.java | 22 +++++ .../ffm/FFMConfinedSwiftMemorySession.java | 40 +++++++++ .../swift/swiftkit/ffm/FFMSwiftInstance.java | 69 +++++++++++++++ .../swiftkit/ffm/FFMSwiftInstanceCleanup.java | 22 +++-- .../swiftkit/ffm}/MemorySegmentUtils.java | 2 +- .../org/swift/swiftkit/ffm}/SwiftAnyType.java | 7 +- .../swift/swiftkit/ffm}/SwiftHeapObject.java | 5 +- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 58 ++----------- .../swift/swiftkit/ffm}/SwiftValueLayout.java | 2 +- .../swiftkit/ffm}/SwiftValueWitnessTable.java | 15 ++-- .../swift/swiftkit/ffm}/AutoArenaTest.java | 15 +--- .../ffm}/SwiftRuntimeMetadataTest.java | 6 +- .../ClassPrintingTests.swift | 2 +- .../FuncCallbackImportTests.swift | 20 ++--- .../FunctionDescriptorImportTests.swift | 20 ++--- .../MethodImportTests.swift | 8 +- .../StringPassingTests.swift | 6 +- .../VariableImportTests.swift | 8 +- settings.gradle | 3 +- 62 files changed, 689 insertions(+), 382 deletions(-) rename Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/{ => ffm}/JavaToSwiftBenchmark.java (98%) rename Samples/{SwiftKitSampleApp/src/test/java/org/swift/swiftkit => SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm}/MySwiftClassTest.java (72%) rename Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/{ => ffm}/SwiftArenaTest.java (77%) rename Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/{ => ffm}/JavaToSwiftBenchmark.java (92%) rename Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/{ => ffm}/StringPassingBenchmark.java (94%) rename Samples/{SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit => SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm}/MySwiftClassTest.java (66%) rename Samples/SwiftKitSampleApp/src/test/java/org/swift/{swiftkit => swiftkitffm}/MySwiftStructTest.java (86%) rename Samples/SwiftKitSampleApp/src/test/java/org/swift/{swiftkit => swiftkitffm}/SwiftArenaTest.java (84%) rename {SwiftKit => SwiftKitCore}/build.gradle (87%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/ClosableSwiftArena.java (96%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/ConfinedSwiftMemorySession.java (68%) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/SwiftArena.java (54%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/SwiftInstance.java (70%) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/SwiftValue.java (94%) rename SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java => SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java (67%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/util/PlatformUtils.java (97%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/util/StringUtils.java (96%) create mode 100644 SwiftKitFFM/build.gradle rename SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java (78%) create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java rename SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java (63%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/MemorySegmentUtils.java (97%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftAnyType.java (89%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftHeapObject.java (91%) rename SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java (88%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftValueLayout.java (98%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftValueWitnessTable.java (94%) rename {SwiftKit/src/test/java/org/swift/swiftkit => SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm}/AutoArenaTest.java (78%) rename {SwiftKit/src/test/java/org/swift/swiftkit => SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm}/SwiftRuntimeMetadataTest.java (95%) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 33fa1f76..c63b87cf 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -11,6 +11,7 @@ runs: java-version: | 24 21 + 19 cache: 'gradle' - name: Set JAVA_HOME_{N} shell: bash diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b55e0489..9a0b59b8 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -31,10 +31,14 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: Gradle :SwiftKit:build - run: ./gradlew build -x test - - name: Gradle :SwiftKit:check - run: ./gradlew :SwiftKit:check --info + - name: Gradle :SwiftKitCore:build + run: ./gradlew :SwiftKitCore:build -x test + - name: Gradle :SwiftKitCore:check + run: ./gradlew :SwiftKitCore:check --info + - name: Gradle :SwiftKitFFM:build + run: ./gradlew :SwiftKitFFM:build -x test + - name: Gradle :SwiftKitFFM:check + run: ./gradlew :SwiftKitFFM:check --info test-java-macos: name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) @@ -51,10 +55,14 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: Gradle :SwiftKit:build - run: ./gradlew build -x test - - name: Gradle :SwiftKit:check - run: ./gradlew :SwiftKit:check --debug + - name: Gradle :SwiftKitCore:build + run: ./gradlew :SwiftKitCore:build -x test + - name: Gradle :SwiftKitCore:check + run: ./gradlew :SwiftKitCore:check --debug + - name: Gradle :SwiftKitFFM:build + run: ./gradlew :SwiftKitFFM:build -x test + - name: Gradle :SwiftKitFFM:check + run: ./gradlew :SwiftKitFFM:check --debug benchmark-java: name: Benchmark (JMH) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) 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 fb10bee0..df0a6633 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 @@ -31,21 +31,6 @@ repositories { mavenCentral() } -testing { - suites { - val test by getting(JvmTestSuite::class) { - useJUnitJupiter("5.10.3") - } - } -} - -/// Enable access to preview APIs, e.g. java.lang.foreign.* (Panama) -tasks.withType(JavaCompile::class).forEach { - it.options.compilerArgs.add("--enable-preview") - it.options.compilerArgs.add("-Xlint:preview") -} - - fun getSwiftRuntimeLibraryPaths(): List { val process = ProcessBuilder("swiftc", "-print-target-info") .redirectError(ProcessBuilder.Redirect.INHERIT) diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/JExtractJNISampleApp/build.gradle index 03794ff7..54bd725a 100644 --- a/Samples/JExtractJNISampleApp/build.gradle +++ b/Samples/JExtractJNISampleApp/build.gradle @@ -148,7 +148,7 @@ tasks.clean { } dependencies { - implementation(project(':SwiftKit')) + implementation(project(':SwiftKitCore')) testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 7d0d77e1..66366a19 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -18,12 +18,12 @@ // Import javakit/swiftkit support libraries -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; public class HelloJava2SwiftJNI { public static void main(String[] args) { - System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath()); + System.out.print("Property: java.library.path = " + SwiftLibraries.getJavaLibraryPath()); examples(); } @@ -35,10 +35,8 @@ static void examples() { MySwiftLibrary.globalTakeIntInt(1337, 42); long cnt = MySwiftLibrary.globalWriteString("String from Java"); - SwiftKit.trace("count = " + cnt); long i = MySwiftLibrary.globalMakeInt(); - SwiftKit.trace("globalMakeInt() = " + i); MySwiftClass.method(); diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index cc4c79d4..96b25e22 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -43,7 +43,8 @@ java { } dependencies { - implementation(project(':SwiftKit')) + implementation(project(':SwiftKitCore')) + implementation(project(':SwiftKitFFM')) testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh index f56b611e..4fde0ef0 100755 --- a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -27,9 +27,10 @@ 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/*" +SWIFTKIT_CORE_CLASSPATH="$(pwd)/../../SwiftKitCore/build/libs/*" +SWIFTKIT_FFM_CLASSPATH="$(pwd)/../../SwiftKitFFM/build/libs/*" MYLIB_CLASSPATH="$(pwd)/build/libs/*" -CLASSPATH="$(pwd)/:${SWIFTKIT_CLASSPATH}:${MYLIB_CLASSPATH}" +CLASSPATH="$(pwd)/:${SWIFTKIT_FFM_CLASSPATH}:${SWIFTKIT_CORE_CLASSPATH}:${MYLIB_CLASSPATH}" echo "CLASSPATH = ${CLASSPATH}" $JAVAC -cp "${CLASSPATH}" Example.java diff --git a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java similarity index 98% rename from Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java rename to Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java index 4e3edf03..7d6b92dc 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.util.concurrent.TimeUnit; 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 cd8af700..e41547bd 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java @@ -16,15 +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; - -import java.util.Arrays; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; public class HelloJava2Swift { @@ -32,7 +27,7 @@ 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 = " + SwiftLibraries.getJavaLibraryPath()); examples(); } @@ -43,12 +38,12 @@ static void examples() { MySwiftLibrary.globalTakeInt(1337); // Example of using an arena; MyClass.deinit is run at end of scope - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); // just checking retains/releases work - SwiftKit.retain(obj); - SwiftKit.release(obj); + SwiftRuntime.retain(obj); + SwiftRuntime.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 bb46ef3d..6fb7651c 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java @@ -16,8 +16,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftArena; -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; import java.io.File; import java.util.stream.Stream; @@ -27,7 +27,7 @@ public class MySwiftClassTest { void checkPaths(Throwable throwable) { - var paths = SwiftKit.getJavaLibraryPath().split(":"); + var paths = SwiftLibraries.getJavaLibraryPath().split(":"); for (var path : paths) { System.out.println("CHECKING PATH: " + path); Stream.of(new File(path).listFiles()) @@ -42,7 +42,7 @@ void checkPaths(Throwable throwable) { @Test void test_MySwiftClass_voidMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); o.voidMethod(); } catch (Throwable throwable) { @@ -52,7 +52,7 @@ void test_MySwiftClass_voidMethod() { @Test void test_MySwiftClass_makeIntMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.makeIntMethod(); assertEquals(12, got); @@ -62,7 +62,7 @@ void test_MySwiftClass_makeIntMethod() { @Test @Disabled // TODO: Need var mangled names in interfaces void test_MySwiftClass_property_len() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.getLen(); assertEquals(12, got); 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 74df5da8..13ebb1b0 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -14,14 +14,10 @@ package com.example.swift; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftKit; -import java.util.Arrays; import java.util.concurrent.CountDownLatch; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/MySwiftClassTest.java similarity index 72% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java rename to Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/MySwiftClassTest.java index 78da5a64..fb1c5d1a 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/MySwiftClassTest.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,16 +24,16 @@ public class MySwiftClassTest { @Test void call_retain_retainCount_release() { - var arena = SwiftArena.ofConfined(); + var arena = AllocatingSwiftArena.ofConfined(); var obj = MySwiftClass.init(1, 2, arena); - assertEquals(1, SwiftKit.retainCount(obj)); + assertEquals(1, SwiftRuntime.retainCount(obj)); // TODO: test directly on SwiftHeapObject inheriting obj - SwiftKit.retain(obj); - assertEquals(2, SwiftKit.retainCount(obj)); + SwiftRuntime.retain(obj); + assertEquals(2, SwiftRuntime.retainCount(obj)); - SwiftKit.release(obj); - assertEquals(1, SwiftKit.retainCount(obj)); + SwiftRuntime.release(obj); + assertEquals(1, SwiftRuntime.retainCount(obj)); } } diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/SwiftArenaTest.java similarity index 77% rename from Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java rename to Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/SwiftArenaTest.java index e8b1ac04..8d786d95 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/SwiftArenaTest.java @@ -12,20 +12,15 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import com.example.swift.MySwiftClass; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; -import org.swift.swiftkit.util.PlatformUtils; +import org.swift.swiftkit.core.util.PlatformUtils; -import java.util.Arrays; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; -import static org.swift.swiftkit.SwiftKit.*; -import static org.swift.swiftkit.SwiftKit.retainCount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.swift.swiftkit.ffm.SwiftRuntime.*; public class SwiftArenaTest { @@ -38,7 +33,7 @@ static boolean isAmd64() { @Test @DisabledIf("isAmd64") public void arena_releaseClassOnClose_class_ok() { - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var obj = MySwiftClass.init(1, 2, arena); retain(obj); diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle index 647eaba9..048c984d 100644 --- a/Samples/SwiftKitSampleApp/build.gradle +++ b/Samples/SwiftKitSampleApp/build.gradle @@ -16,7 +16,6 @@ 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") @@ -148,7 +147,8 @@ tasks.clean { } dependencies { - implementation(project(':SwiftKit')) + implementation(project(':SwiftKitCore')) + implementation(project(':SwiftKitFFM')) testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java similarity index 92% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java rename to Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java index ff313fc6..dffbd090 100644 --- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java +++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import com.example.swift.HelloJava2Swift; import com.example.swift.MySwiftLibrary; @@ -31,12 +31,12 @@ public class JavaToSwiftBenchmark { @State(Scope.Benchmark) public static class BenchmarkState { - ClosableSwiftArena arena; + ClosableAllocatingSwiftArena arena; MySwiftClass obj; @Setup(Level.Trial) public void beforeAll() { - arena = SwiftArena.ofConfined(); + arena = AllocatingSwiftArena.ofConfined(); obj = MySwiftClass.init(1, 2, arena); } diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java similarity index 94% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java rename to Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java index 0e686fb4..25855842 100644 --- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java +++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java @@ -12,15 +12,14 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import com.example.swift.HelloJava2Swift; import com.example.swift.MySwiftClass; import com.example.swift.MySwiftLibrary; import org.openjdk.jmh.annotations.*; +import org.swift.swiftkit.core.ClosableSwiftArena; -import java.lang.foreign.Arena; -import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @@ -40,12 +39,12 @@ public class StringPassingBenchmark { public int stringLen; public String string; - ClosableSwiftArena arena; + ClosableAllocatingSwiftArena arena; MySwiftClass obj; @Setup(Level.Trial) public void beforeAll() { - arena = SwiftArena.ofConfined(); + arena = AllocatingSwiftArena.ofConfined(); 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 c12d82dd..668140b2 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -18,8 +18,9 @@ // Import javakit/swiftkit support libraries -import org.swift.swiftkit.SwiftArena; -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; public class HelloJava2Swift { @@ -27,7 +28,7 @@ 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 = " + SwiftLibraries.getJavaLibraryPath()); examples(); } @@ -39,30 +40,30 @@ static void examples() { long cnt = MySwiftLibrary.globalWriteString("String from Java"); - SwiftKit.trace("count = " + cnt); + SwiftRuntime.trace("count = " + cnt); MySwiftLibrary.globalCallMeRunnable(() -> { - SwiftKit.trace("running runnable"); + SwiftRuntime.trace("running runnable"); }); - SwiftKit.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); + SwiftRuntime.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); MySwiftLibrary.withBuffer((buf) -> { - SwiftKit.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + SwiftRuntime.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); }); // Example of using an arena; MyClass.deinit is run at end of scope - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(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)); + SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + SwiftRuntime.retain(obj); + SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + SwiftRuntime.release(obj); + SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); obj.setCounter(12); - SwiftKit.trace("obj.counter = " + obj.getCounter()); + SwiftRuntime.trace("obj.counter = " + obj.getCounter()); obj.voidMethod(); obj.takeIntMethod(42); @@ -71,9 +72,9 @@ static void examples() { otherObj.voidMethod(); MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena); - SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity()); + SwiftRuntime.trace("swiftValue.capacity = " + swiftValue.getCapacity()); swiftValue.withCapLen((cap, len) -> { - SwiftKit.trace("withCapLenCallback: cap=" + cap + ", len=" + len); + SwiftRuntime.trace("withCapLenCallback: cap=" + cap + ", len=" + len); }); } 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 6c0ceb1e..1f0464eb 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -16,8 +16,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftArena; -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; import java.io.File; import java.util.stream.Stream; @@ -27,7 +27,7 @@ public class MySwiftClassTest { void checkPaths(Throwable throwable) { - var paths = SwiftKit.getJavaLibraryPath().split(":"); + var paths = SwiftLibraries.getJavaLibraryPath().split(":"); for (var path : paths) { Stream.of(new File(path).listFiles()) .filter(file -> !file.isDirectory()) @@ -41,7 +41,7 @@ void checkPaths(Throwable throwable) { @Test void test_MySwiftClass_voidMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); o.voidMethod(); } catch (Throwable throwable) { @@ -51,7 +51,7 @@ void test_MySwiftClass_voidMethod() { @Test void test_MySwiftClass_makeIntMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.makeIntMethod(); assertEquals(12, got); @@ -61,7 +61,7 @@ void test_MySwiftClass_makeIntMethod() { @Test @Disabled // TODO: Need var mangled names in interfaces void test_MySwiftClass_property_len() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.getLen(); assertEquals(12, got); 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 2df53843..a6c34b57 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -14,15 +14,10 @@ package com.example.swift; -import com.example.swift.MySwiftLibrary; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftKit; -import java.util.Arrays; import java.util.concurrent.CountDownLatch; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java similarity index 66% rename from Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java index 78da5a64..17db4c41 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java @@ -12,28 +12,30 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkitffm; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import com.example.swift.MySwiftClass; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; public class MySwiftClassTest { @Test void call_retain_retainCount_release() { - var arena = SwiftArena.ofConfined(); + var arena = AllocatingSwiftArena.ofConfined(); var obj = MySwiftClass.init(1, 2, arena); - assertEquals(1, SwiftKit.retainCount(obj)); + assertEquals(1, SwiftRuntime.retainCount(obj)); // TODO: test directly on SwiftHeapObject inheriting obj - SwiftKit.retain(obj); - assertEquals(2, SwiftKit.retainCount(obj)); + SwiftRuntime.retain(obj); + assertEquals(2, SwiftRuntime.retainCount(obj)); - SwiftKit.release(obj); - assertEquals(1, SwiftKit.retainCount(obj)); + SwiftRuntime.release(obj); + assertEquals(1, SwiftRuntime.retainCount(obj)); } } diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java similarity index 86% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java index 843551a5..6b994137 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkitffm; import com.example.swift.MySwiftStruct; import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -23,7 +24,7 @@ public class MySwiftStructTest { @Test void create_struct() { - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { long cap = 12; long len = 34; var struct = MySwiftStruct.init(cap, len, arena); diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java similarity index 84% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java index 52d791a1..08dd9b1b 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java @@ -12,21 +12,18 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkitffm; 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; -import org.swift.swiftkit.util.PlatformUtils; - -import java.util.Arrays; -import java.util.stream.Collectors; +import org.swift.swiftkit.core.util.PlatformUtils; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; import static org.junit.jupiter.api.Assertions.*; -import static org.swift.swiftkit.SwiftKit.*; -import static org.swift.swiftkit.SwiftKit.retainCount; +import static org.swift.swiftkit.ffm.SwiftRuntime.retainCount; public class SwiftArenaTest { @@ -39,13 +36,13 @@ static boolean isAmd64() { @Test @DisabledIf("isAmd64") public void arena_releaseClassOnClose_class_ok() { - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var obj = MySwiftClass.init(1, 2, arena); - retain(obj); + SwiftRuntime.retain(obj); assertEquals(2, retainCount(obj)); - release(obj); + SwiftRuntime.release(obj); assertEquals(1, retainCount(obj)); } } @@ -56,7 +53,7 @@ public void arena_releaseClassOnClose_class_ok() { public void arena_markAsDestroyed_preventUseAfterFree_class() { MySwiftClass unsafelyEscapedOutsideArenaScope = null; - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var obj = MySwiftClass.init(1, 2, arena); unsafelyEscapedOutsideArenaScope = obj; } @@ -75,7 +72,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() { public void arena_markAsDestroyed_preventUseAfterFree_struct() { MySwiftStruct unsafelyEscapedOutsideArenaScope = null; - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var s = MySwiftStruct.init(1, 2, arena); unsafelyEscapedOutsideArenaScope = s; } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 364fd270..13230a32 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -116,8 +116,8 @@ extension FFMSwift2JavaGenerator { """ public static \(returnTy) call(\(paramsStr)) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(\(argsStr)); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(\(argsStr)); } \(maybeReturn)HANDLE.invokeExact(\(argsStr)); } catch (Throwable ex$) { @@ -156,7 +156,7 @@ extension FFMSwift2JavaGenerator { /// apply(); /// } /// static final MethodDescriptor DESC = FunctionDescriptor.of(...); - /// static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + /// static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); /// static MemorySegment toUpcallStub(Function fi, Arena arena) { /// return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); /// } @@ -197,7 +197,7 @@ extension FFMSwift2JavaGenerator { printFunctionDescriptorDefinition(&printer, cResultType, cParams) printer.print( """ - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -320,7 +320,7 @@ extension FFMSwift2JavaGenerator { .flatMap(\.javaParameters) .map { "\($0.type) \($0.name)" } if translatedSignature.requiresSwiftArena { - paramDecls.append("SwiftArena swiftArena$") + paramDecls.append("AllocatingSwiftArena swiftArena$") } // TODO: we could copy the Swift method's documentation over here, that'd be great UX diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 16efddea..d29f6b96 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -313,7 +313,7 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType")) + name: parameterName, type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType")) ], conversion: .swiftValueSelfSegment(.placeholder) ) @@ -350,7 +350,7 @@ extension FFMSwift2JavaGenerator { name: parameterName, type: .javaLangString ) ], - conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) + conversion: .call(.placeholder, function: "SwiftRuntime.toCString", withArena: true) ) default: @@ -411,7 +411,7 @@ extension FFMSwift2JavaGenerator { switch swiftType { case .metatype(_): // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' - let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType") + let javaType = JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType") return TranslatedResult( javaResultType: javaType, outParameters: [], diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 09fcf858..9a140b94 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -92,9 +92,10 @@ 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.*", + "org.swift.swiftkit.core.*", + "org.swift.swiftkit.core.util.*", + "org.swift.swiftkit.ffm.*", + "org.swift.swiftkit.ffm.SwiftRuntime", // Necessary for native calls and type mapping "java.lang.foreign.*", @@ -188,7 +189,7 @@ extension FFMSwift2JavaGenerator { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { - System.loadLibrary(SwiftKit.STDLIB_DYLIB_NAME); + System.loadLibrary(SwiftLibraries.STDLIB_DYLIB_NAME); System.loadLibrary("SwiftKitSwift"); System.loadLibrary(LIB_NAME); return true; @@ -210,7 +211,7 @@ extension FFMSwift2JavaGenerator { printer.print( """ - public \(decl.swiftNominal.name)(MemorySegment segment, SwiftArena arena) { + public \(decl.swiftNominal.name)(MemorySegment segment, AllocatingSwiftArena arena) { super(segment, arena); } """ @@ -272,7 +273,7 @@ extension FFMSwift2JavaGenerator { parentProtocol = "SwiftValue" } - printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends SwiftInstance implements \(parentProtocol)") { + printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends FFMSwiftInstance implements \(parentProtocol)") { printer in // Constants printClassConstants(printer: &printer) @@ -322,9 +323,9 @@ extension FFMSwift2JavaGenerator { static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup(); private static SymbolLookup getSymbolLookup() { // Ensure Swift and our Lib are loaded during static initialization of the class. - SwiftKit.loadLibrary("swiftCore"); - SwiftKit.loadLibrary("SwiftKitSwift"); - SwiftKit.loadLibrary(LIB_NAME); + SwiftLibraries.loadLibrary("swiftCore"); + SwiftLibraries.loadLibrary("SwiftKitSwift"); + SwiftLibraries.loadLibrary(LIB_NAME); if (PlatformUtils.isMacOS()) { return SymbolLookup.libraryLookup(System.mapLibraryName(LIB_NAME), LIBRARY_ARENA) @@ -386,7 +387,7 @@ extension FFMSwift2JavaGenerator { public String toString() { return getClass().getSimpleName() + "(" - + SwiftKit.nameOfSwiftType($swiftType().$memorySegment(), true) + + SwiftRuntime.nameOfSwiftType($swiftType().$memorySegment(), true) + ")@" + $memorySegment(); } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c61dd929..b4d73873 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -130,7 +130,7 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc ) { let translatedDecl = self.translatedDecl(for: decl) - let parentName = translatedDecl.parentName ?? swiftModuleName + let parentName = translatedDecl.parentName let swiftReturnType = decl.functionSignature.result.type printCDecl(&printer, decl) { printer in diff --git a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift index aa01502d..0afc9645 100644 --- a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift +++ b/Sources/JExtractSwiftLib/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.swiftNominal.qualifiedName)") + SwiftRuntime.swiftjava.getType("\(module)", "\(nominal.swiftNominal.qualifiedName)") """ } } diff --git a/SwiftKit/build.gradle b/SwiftKitCore/build.gradle similarity index 87% rename from SwiftKit/build.gradle rename to SwiftKitCore/build.gradle index 8f3b6c51..3d5907f2 100644 --- a/SwiftKit/build.gradle +++ b/SwiftKitCore/build.gradle @@ -25,13 +25,15 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + // JDK 19 is the last JDK to support Java 7 + languageVersion.set(JavaLanguageVersion.of(19)) } + // Support Android 6+ (Java 7) + sourceCompatibility = JavaVersion.VERSION_1_7 } dependencies { - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation 'junit:junit:4.13.2' } tasks.test { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ClosableSwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ClosableSwiftArena.java similarity index 96% rename from SwiftKit/src/main/java/org/swift/swiftkit/ClosableSwiftArena.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/ClosableSwiftArena.java index c257ae57..1a089069 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ClosableSwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ClosableSwiftArena.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; /** * Auto-closable version of {@link SwiftArena}. @@ -24,5 +24,4 @@ public interface ClosableSwiftArena extends SwiftArena, AutoCloseable { * Throws if unable to verify all resources have been release (e.g. over retained Swift classes) */ void close(); - } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java similarity index 68% rename from SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 86725ae8..cd8f2dd1 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -12,15 +12,13 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -final class ConfinedSwiftMemorySession implements ClosableSwiftArena { +public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final static int CLOSED = 0; final static int ACTIVE = 1; @@ -28,21 +26,17 @@ 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 { if (this.owner != null && this.owner != Thread.currentThread()) { - throw new WrongThreadException("ConfinedSwift arena is confined to %s but was closed from %s!" - .formatted(this.owner, Thread.currentThread())); + throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); } else if (this.state.get() < ACTIVE) { throw new RuntimeException("SwiftArena is already closed!"); } @@ -53,32 +47,19 @@ public void close() { checkValid(); // Cleanup all resources - if (this.state.compareAndExchange(ACTIVE, CLOSED) == ACTIVE) { + if (this.state.compareAndSet(ACTIVE, CLOSED)) { this.resources.runCleanup(); } // else, was already closed; do nothing - - this.arena.close(); } @Override public void register(SwiftInstance instance) { checkValid(); - var statusDestroyedFlag = instance.$statusDestroyedFlag(); - Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); - - var cleanup = new SwiftInstanceCleanup( - instance.$memorySegment(), - instance.$swiftType(), - markAsDestroyed); + SwiftInstanceCleanup cleanup = instance.createCleanupAction(); 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<>(); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java new file mode 100644 index 00000000..891745a2 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class JNISwiftInstance extends SwiftInstance { + /** + * The designated constructor of any imported Swift types. + * + * @param pointer a pointer to the memory containing the value + * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ + protected JNISwiftInstance(long pointer, SwiftArena arena) { + super(pointer, arena); + } + + /** + * Creates a function that will be called when the value should be destroyed. + * This will be code-generated to call a native method to do deinitialization and deallocation. + *

+ * The reason for this "indirection" is that we cannot have static methods on abstract classes, + * and we can't define the destroy method as a member method, because we assume that the wrapper + * has been released, when we destroy. + *

+ * Warning: The function must not capture {@code this}. + * + * @return a function that is called when the value should be destroyed. + */ + abstract Runnable $createDestroyFunction(); + + @Override + public SwiftInstanceCleanup createCleanupAction() { + final AtomicBoolean statusDestroyedFlag = $statusDestroyedFlag(); + Runnable markAsDestroyed = new Runnable() { + @Override + public void run() { + statusDestroyedFlag.set(true); + } + }; + + return new JNISwiftInstanceCleanup(this.$createDestroyFunction(), markAsDestroyed); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java new file mode 100644 index 00000000..08ddcdab --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +class JNISwiftInstanceCleanup implements SwiftInstanceCleanup { + private final Runnable destroyFunction; + private final Runnable markAsDestroyed; + + public JNISwiftInstanceCleanup(Runnable destroyFunction, Runnable markAsDestroyed) { + this.destroyFunction = destroyFunction; + this.markAsDestroyed = markAsDestroyed; + } + + @Override + public void run() { + markAsDestroyed.run(); + destroyFunction.run(); + } +} diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java similarity index 54% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index f50025cc..ed16d250 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -12,10 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; - -import java.lang.foreign.SegmentAllocator; -import java.util.concurrent.ThreadFactory; +package org.swift.swiftkit.core; /** * A Swift arena manages Swift allocated memory for classes, structs, enums etc. @@ -24,39 +21,17 @@ *

A confined arena has an associated owner thread that confines some operations to * associated owner thread such as {@link ClosableSwiftArena#close()}. */ -public interface SwiftArena extends SegmentAllocator { - - static ClosableSwiftArena ofConfined() { - return new ConfinedSwiftMemorySession(Thread.currentThread()); - } - - static SwiftArena ofAuto() { - ThreadFactory cleanerThreadFactory = r -> new Thread(r, "AutoSwiftArenaCleanerThread"); - return new AutoSwiftMemorySession(cleanerThreadFactory); - } - +public interface SwiftArena { /** * Register a Swift object. * Its memory should be considered managed by this arena, and be destroyed when the arena is closed. */ void register(SwiftInstance instance); - } /** * Represents a list of resources that need a cleanup, e.g. allocated classes/structs. */ interface SwiftResourceList { - void runCleanup(); - -} - - -final class UnexpectedRetainCountException extends RuntimeException { - public UnexpectedRetainCountException(Object resource, long retainCount, int expectedRetainCount) { - super(("Attempting to cleanup managed memory segment %s, but it's retain count was different than [%d] (was %d)! " + - "This would result in destroying a swift object that is still retained by other code somewhere." - ).formatted(resource, expectedRetainCount, retainCount)); - } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java similarity index 70% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java index 5b8ff700..638cb8be 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java @@ -12,24 +12,28 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; -import java.lang.foreign.GroupLayout; -import java.lang.foreign.MemorySegment; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; public abstract class SwiftInstance { /// Pointer to the "self". - private final MemorySegment selfMemorySegment; + private final long selfPointer; /** * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. */ - public final MemorySegment $memorySegment() { - return this.selfMemorySegment; + public final long pointer() { + return this.selfPointer; } + /** + * Called when the arena has decided the value should be destroyed. + *

+ * Warning: The cleanup action must not capture {@code this}. + */ + public abstract SwiftInstanceCleanup createCleanupAction(); + // 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); @@ -45,24 +49,14 @@ public abstract class SwiftInstance { return this.$state$destroyed; } - /** - * The in memory layout of an instance of this Swift type. - */ - public abstract GroupLayout $layout(); - - /** - * The Swift type metadata of this type. - */ - public abstract SwiftAnyType $swiftType(); - /** * The designated constructor of any imported Swift types. * - * @param segment the memory segment. + * @param pointer a pointer to the memory containing the value * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. */ - protected SwiftInstance(MemorySegment segment, SwiftArena arena) { - this.selfMemorySegment = segment; + protected SwiftInstance(long pointer, SwiftArena arena) { + this.selfPointer = pointer; arena.register(this); } @@ -78,13 +72,4 @@ protected SwiftInstance(MemorySegment segment, SwiftArena arena) { 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/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java new file mode 100644 index 00000000..56416968 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +/** + * A Swift memory instance cleanup, e.g. count-down a reference count and destroy a class, or destroy struct/enum etc. + */ +public interface SwiftInstanceCleanup extends Runnable {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java new file mode 100644 index 00000000..2abdaff5 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import org.swift.swiftkit.core.util.PlatformUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +public final class SwiftLibraries { + + public static final String STDLIB_DYLIB_NAME = "swiftCore"; + public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + public static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); + + private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = loadLibraries(false); + + public static boolean loadLibraries(boolean loadSwiftKit) { + System.loadLibrary(STDLIB_DYLIB_NAME); + if (loadSwiftKit) { + System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + } + return true; + } + + // ==== ------------------------------------------------------------------------------------------------------------ + // Loading libraries + + public static void loadLibrary(String libname) { + // TODO: avoid concurrent loadResource calls; one load is enough esp since we cause File IO when we do that + try { + // try to load a dylib from our classpath, e.g. when we included it in our jar + loadResourceLibrary(libname); + } catch (UnsatisfiedLinkError | RuntimeException e) { + // fallback to plain system path loading + System.loadLibrary(libname); + } + } + + public static void loadResourceLibrary(String libname) { + String resourceName = PlatformUtils.dynamicLibraryName(libname); + if (SwiftLibraries.TRACE_DOWNCALLS) { + System.out.println("[swift-java] Loading resource library: " + resourceName); + } + + try (InputStream libInputStream = SwiftLibraries.class.getResourceAsStream("/" + resourceName)) { + if (libInputStream == null) { + throw new RuntimeException("Expected library '" + libname + "' ('" + resourceName + "') was not found as resource!"); + } + + // TODO: we could do an in memory file system here + File tempFile = File.createTempFile(libname, ""); + tempFile.deleteOnExit(); + Files.copy(libInputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + System.load(tempFile.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException("Failed to load dynamic library '" + libname + "' ('" + resourceName + "') as resource!", e); + } + } + + public static String getJavaLibraryPath() { + return System.getProperty("java.library.path"); + } +} diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftValue.java similarity index 94% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftValue.java index a364c012..3e9c1aff 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftValue.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; /** * Represent a wrapper around a Swift value object. e.g. {@code struct} or {@code enum}. diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java similarity index 67% rename from SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java index da25ae4d..d15436f9 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java @@ -12,14 +12,10 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; - -import java.lang.foreign.MemorySegment; - -public interface ManagedSwiftType { - /** - * The memory segment of `self` of the managed Swift object/value. - */ - public MemorySegment $memorySegment(); +package org.swift.swiftkit.core; +public class WrongThreadException extends RuntimeException { + public WrongThreadException(String message) { + super(message); + } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/util/PlatformUtils.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/PlatformUtils.java similarity index 97% rename from SwiftKit/src/main/java/org/swift/swiftkit/util/PlatformUtils.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/PlatformUtils.java index 6addb31d..667bd53b 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/util/PlatformUtils.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/PlatformUtils.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit.util; +package org.swift.swiftkit.core.util; public class PlatformUtils { public static boolean isLinux() { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/util/StringUtils.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/StringUtils.java similarity index 96% rename from SwiftKit/src/main/java/org/swift/swiftkit/util/StringUtils.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/StringUtils.java index 6753ea73..1ce4259c 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/util/StringUtils.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/StringUtils.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit.util; +package org.swift.swiftkit.core.util; public class StringUtils { public static String stripPrefix(String mangledName, String prefix) { diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle new file mode 100644 index 00000000..22f88001 --- /dev/null +++ b/SwiftKitFFM/build.gradle @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +plugins { + id("build-logic.java-application-conventions") +} + +group = "org.swift.swiftkit" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(24)) + } +} + +dependencies { + implementation(project(':SwiftKitCore')) + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +testing { + suites { + test { + useJUnitJupiter('5.10.3') + } + } +} + +tasks.test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } +} + +/// Enable access to preview APIs, e.g. java.lang.foreign.* (Panama) +tasks.withType(JavaCompile).configureEach { + options.compilerArgs.add("--enable-preview") + options.compilerArgs.add("-Xlint:preview") +} + +// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) + +def compileSwift = tasks.register("compileSwift", Exec) { + description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + + inputs.file(new File(rootDir, "Package.swift")) + inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes + outputs.dir(new File(rootDir, ".build")) + + workingDir = rootDir + commandLine "swift" + args("build", "--target", "SwiftKitSwift") +} +tasks.build { + dependsOn("compileSwift") +} + + + +def cleanSwift = tasks.register("cleanSwift", Exec) { + workingDir = rootDir + commandLine "swift" + args("package", "clean") +} +tasks.clean { + dependsOn("cleanSwift") +} + diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java similarity index 78% rename from SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java index ecbe836e..fecd5202 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java @@ -12,7 +12,10 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftArena; +import org.swift.swiftkit.core.SwiftInstance; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -26,7 +29,7 @@ *

When registered Java wrapper classes around native Swift instances {@link SwiftInstance}, * are eligible for collection, this will trigger the cleanup of the native resources as well. * - *

This memory session is LESS reliable than using a {@link ConfinedSwiftMemorySession} because + *

This memory session is LESS reliable than using a {@link FFMConfinedSwiftMemorySession} because * the timing of when the native resources are cleaned up is somewhat undefined, and rely on the * system GC. Meaning, that if an object nas been promoted to an old generation, there may be a * long time between the resource no longer being referenced "in Java" and its native memory being released, @@ -37,12 +40,12 @@ * *

Whenever possible, prefer using an explicitly managed {@link SwiftArena}, such as {@link SwiftArena#ofConfined()}. */ -final class AutoSwiftMemorySession implements SwiftArena { +final class AllocatingAutoSwiftMemorySession implements AllocatingSwiftArena { private final Arena arena; private final Cleaner cleaner; - public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { + public AllocatingAutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { this.cleaner = Cleaner.create(cleanerThreadFactory); this.arena = Arena.ofAuto(); } @@ -51,12 +54,9 @@ public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { 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 = instance.$statusDestroyedFlag(); - Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); - - MemorySegment resource = instance.$memorySegment(); - var cleanupAction = new SwiftInstanceCleanup(resource, instance.$swiftType(), markAsDestroyed); + // We make sure we don't capture `instance` in the + // cleanup action, so we can ignore the warning below. + var cleanupAction = instance.createCleanupAction(); cleaner.register(instance, cleanupAction); } @@ -64,4 +64,4 @@ public void register(SwiftInstance instance) { public MemorySegment allocate(long byteSize, long byteAlignment) { return arena.allocate(byteSize, byteAlignment); } -} +} \ No newline at end of file diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java new file mode 100644 index 00000000..08fad15b --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftArena; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.util.concurrent.ThreadFactory; + +public interface AllocatingSwiftArena extends SwiftArena, SegmentAllocator { + MemorySegment allocate(long byteSize, long byteAlignment); + + static ClosableAllocatingSwiftArena ofConfined() { + return new FFMConfinedSwiftMemorySession(Thread.currentThread()); + } + + static AllocatingSwiftArena ofAuto() { + ThreadFactory cleanerThreadFactory = r -> new Thread(r, "AutoSwiftArenaCleanerThread"); + return new AllocatingAutoSwiftMemorySession(cleanerThreadFactory); + } +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java new file mode 100644 index 00000000..70cdcbb6 --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.ClosableSwiftArena; + +/** + * Auto-closable version of {@link AllocatingSwiftArena}. + */ +public interface ClosableAllocatingSwiftArena extends ClosableSwiftArena, AllocatingSwiftArena {} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java new file mode 100644 index 00000000..424e54b6 --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +final class FFMConfinedSwiftMemorySession extends ConfinedSwiftMemorySession implements AllocatingSwiftArena, ClosableAllocatingSwiftArena { + final Arena arena; + + public FFMConfinedSwiftMemorySession(Thread owner) { + super(owner); + this.arena = Arena.ofConfined(); + } + + @Override + public void close() { + super.close(); + this.arena.close(); + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + return arena.allocate(byteSize, byteAlignment); + } +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java new file mode 100644 index 00000000..f4a01aa4 --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java @@ -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 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftInstance; +import org.swift.swiftkit.core.SwiftInstanceCleanup; + +import java.lang.foreign.MemorySegment; + +public abstract class FFMSwiftInstance extends SwiftInstance { + private final MemorySegment memorySegment; + + /** + * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. + */ + public final MemorySegment $memorySegment() { + return this.memorySegment; + } + + /** + * The Swift type metadata of this type. + */ + public abstract SwiftAnyType $swiftType(); + + /** + * The designated constructor of any imported Swift types. + * + * @param segment the memory segment. + * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ + protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { + super(segment.address(), arena); + this.memorySegment = segment; + } + + @Override + public SwiftInstanceCleanup createCleanupAction() { + var statusDestroyedFlag = $statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); + + return new FFMSwiftInstanceCleanup( + $memorySegment(), + $swiftType(), + markAsDestroyed + ); + } + + + /** + * 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/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java similarity index 63% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java index a9fdd9c8..a5d0829a 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java @@ -12,18 +12,22 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftInstanceCleanup; import java.lang.foreign.MemorySegment; -/** - * A Swift memory instance cleanup, e.g. count-down a reference count and destroy a class, or destroy struct/enum etc. - */ -record SwiftInstanceCleanup( - MemorySegment selfPointer, - SwiftAnyType selfType, - Runnable markAsDestroyed -) implements Runnable { +public class FFMSwiftInstanceCleanup implements SwiftInstanceCleanup { + private final MemorySegment selfPointer; + private final SwiftAnyType selfType; + private final Runnable markAsDestroyed; + + public FFMSwiftInstanceCleanup(MemorySegment selfPointer, SwiftAnyType selfType, Runnable markAsDestroyed) { + this.selfPointer = selfPointer; + this.selfType = selfType; + this.markAsDestroyed = markAsDestroyed; + } @Override public void run() { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/MemorySegmentUtils.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/MemorySegmentUtils.java similarity index 97% rename from SwiftKit/src/main/java/org/swift/swiftkit/MemorySegmentUtils.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/MemorySegmentUtils.java index 660a5500..0890b1d8 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/MemorySegmentUtils.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/MemorySegmentUtils.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftAnyType.java similarity index 89% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftAnyType.java index 4d13ecb7..3915725b 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftAnyType.java @@ -12,7 +12,10 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.ffm.SwiftRuntime; +import org.swift.swiftkit.ffm.SwiftValueLayout; import java.lang.foreign.GroupLayout; import java.lang.foreign.MemoryLayout; @@ -46,7 +49,7 @@ public SwiftAnyType(MemorySegment memorySegment) { * Get the human-readable Swift type name of this type. */ public String getSwiftName() { - return SwiftKit.nameOfSwiftType(memorySegment, true); + return SwiftRuntime.nameOfSwiftType(memorySegment, true); } @Override diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftHeapObject.java similarity index 91% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftHeapObject.java index 89050fb5..b506be90 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftHeapObject.java @@ -12,7 +12,8 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + import java.lang.foreign.MemorySegment; @@ -25,7 +26,7 @@ public interface SwiftHeapObject { /** * Pointer to the instance. */ - public default MemorySegment $instance() { + default MemorySegment $instance() { return this.$memorySegment().get(SwiftValueLayout.SWIFT_POINTER, 0); } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java similarity index 88% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index 9c04eded..f734bbef 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -12,28 +12,22 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; -import org.swift.swiftkit.util.PlatformUtils; +import org.swift.swiftkit.core.util.PlatformUtils; -import java.io.File; -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; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Optional; import java.util.stream.Collectors; -import static org.swift.swiftkit.util.StringUtils.stripPrefix; -import static org.swift.swiftkit.util.StringUtils.stripSuffix; +import static org.swift.swiftkit.core.util.StringUtils.stripPrefix; +import static org.swift.swiftkit.core.util.StringUtils.stripSuffix; -public class SwiftKit { +public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; @@ -68,7 +62,7 @@ private static SymbolLookup getSymbolLookup() { } } - public SwiftKit() { + public SwiftRuntime() { } public static void traceDowncall(Object... args) { @@ -103,50 +97,10 @@ static MemorySegment findOrThrow(String symbol) { .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: %s".formatted(symbol))); } - public static String getJavaLibraryPath() { - return System.getProperty("java.library.path"); - } - public static boolean getJextractTraceDowncalls() { return Boolean.getBoolean("jextract.trace.downcalls"); } - // ==== ------------------------------------------------------------------------------------------------------------ - // Loading libraries - - public static void loadLibrary(String libname) { - // TODO: avoid concurrent loadResource calls; one load is enough esp since we cause File IO when we do that - try { - // try to load a dylib from our classpath, e.g. when we included it in our jar - loadResourceLibrary(libname); - } catch (UnsatisfiedLinkError | RuntimeException e) { - // fallback to plain system path loading - System.loadLibrary(libname); - } - } - - public static void loadResourceLibrary(String libname) { - var resourceName = PlatformUtils.dynamicLibraryName(libname); - if (SwiftKit.TRACE_DOWNCALLS) { - System.out.println("[swift-java] Loading resource library: " + resourceName); - } - - try (var libInputStream = SwiftKit.class.getResourceAsStream("/" + resourceName)) { - if (libInputStream == null) { - throw new RuntimeException("Expected library '" + libname + "' ('" + resourceName + "') was not found as resource!"); - } - - // TODO: we could do an in memory file system here - var tempFile = File.createTempFile(libname, ""); - tempFile.deleteOnExit(); - Files.copy(libInputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - - System.load(tempFile.getAbsolutePath()); - } catch (IOException e) { - throw new RuntimeException("Failed to load dynamic library '" + libname + "' ('" + resourceName + "') as resource!", e); - } - } - // ==== ------------------------------------------------------------------------------------------------------------ // free diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java similarity index 98% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java index 00215ef0..c0a75144 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.lang.foreign.AddressLayout; import java.lang.foreign.MemoryLayout; diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java similarity index 94% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java index 8ddff1b6..a0a6a79f 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java @@ -12,13 +12,12 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.lang.foreign.*; import java.lang.invoke.*; import static java.lang.foreign.ValueLayout.JAVA_BYTE; -import static org.swift.swiftkit.SwiftKit.getSwiftInt; public abstract class SwiftValueWitnessTable { @@ -86,7 +85,7 @@ public static MemorySegment valueWitnessTable(MemorySegment typeMetadata) { * @param typeMetadata the memory segment must point to a Swift metadata */ public static long sizeOfSwiftType(MemorySegment typeMetadata) { - return getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$size$offset); + return SwiftRuntime.getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$size$offset); } @@ -112,7 +111,7 @@ public static long sizeOfSwiftType(MemorySegment typeMetadata) { * @param typeMetadata the memory segment must point to a Swift metadata */ public static long strideOfSwiftType(MemorySegment typeMetadata) { - return getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$stride$offset); + return SwiftRuntime.getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$stride$offset); } @@ -122,7 +121,7 @@ public static long strideOfSwiftType(MemorySegment typeMetadata) { * @param typeMetadata the memory segment must point to a Swift metadata */ public static long alignmentOfSwiftType(MemorySegment typeMetadata) { - long flags = getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$flags$offset); + long flags = SwiftRuntime.getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$flags$offset); return (flags & 0xFF) + 1; } @@ -156,7 +155,7 @@ public static MemoryLayout layoutOfSwiftType(MemorySegment typeMetadata) { return MemoryLayout.structLayout( layouts - ).withName(SwiftKit.nameOfSwiftType(typeMetadata, true)); + ).withName(SwiftRuntime.nameOfSwiftType(typeMetadata, true)); } @@ -198,7 +197,7 @@ static MemorySegment addr(SwiftAnyType ty) { final var vwt = SwiftValueWitnessTable.valueWitnessTable(ty.$memorySegment()); // Get the address of the destroy function stored at the offset of the witness table - long funcAddress = getSwiftInt(vwt, destroy.$offset); + long funcAddress = SwiftRuntime.getSwiftInt(vwt, destroy.$offset); return MemorySegment.ofAddress(funcAddress); } @@ -258,7 +257,7 @@ static MemorySegment addr(SwiftAnyType ty) { final var vwt = SwiftValueWitnessTable.valueWitnessTable(ty.$memorySegment()); // Get the address of the function stored at the offset of the witness table - long funcAddress = getSwiftInt(vwt, initializeWithCopy.$offset); + long funcAddress = SwiftRuntime.getSwiftInt(vwt, initializeWithCopy.$offset); return MemorySegment.ofAddress(funcAddress); } diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java similarity index 78% rename from SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java rename to SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java index 23365de9..b95f6c45 100644 --- a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java +++ b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java @@ -12,21 +12,19 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import org.junit.jupiter.api.Test; import java.lang.foreign.GroupLayout; import java.lang.foreign.MemorySegment; -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() { - SwiftArena arena = SwiftArena.ofAuto(); + AllocatingSwiftArena arena = AllocatingSwiftArena.ofAuto(); // This object is registered to the arena. var object = new FakeSwiftInstance(arena); @@ -48,16 +46,11 @@ public void cleaner_releases_native_resource() { } } - private static class FakeSwiftInstance extends SwiftInstance implements SwiftHeapObject { - public FakeSwiftInstance(SwiftArena arena) { + private static class FakeSwiftInstance extends FFMSwiftInstance implements SwiftHeapObject { + public FakeSwiftInstance(AllocatingSwiftArena arena) { super(MemorySegment.NULL, arena); } - @Override - public GroupLayout $layout() { - return null; - } - @Override public SwiftAnyType $swiftType() { return null; diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/SwiftRuntimeMetadataTest.java similarity index 95% rename from SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java rename to SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/SwiftRuntimeMetadataTest.java index 3419405e..0ec82123 100644 --- a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java +++ b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/SwiftRuntimeMetadataTest.java @@ -12,11 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +package org.swift.swiftkit.ffm; public class SwiftRuntimeMetadataTest { diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift index 6c0d8d0a..12b11d38 100644 --- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift +++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift @@ -44,7 +44,7 @@ struct ClassPrintingTests { try assertOutput(input: class_interfaceFile, .ffm, .java, swiftModuleName: "__FakeModule", expectedChunks: [ """ public static final SwiftAnyType TYPE_METADATA = - new SwiftAnyType(SwiftKit.swiftjava.getType("__FakeModule", "MySwiftClass")); + new SwiftAnyType(SwiftRuntime.swiftjava.getType("__FakeModule", "MySwiftClass")); public final SwiftAnyType $swiftType() { return TYPE_METADATA; } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index d81dad8c..5f80dd28 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -77,8 +77,8 @@ final class FuncCallbackImportTests { 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); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(callback); } HANDLE.invokeExact(callback); } catch (Throwable ex$) { @@ -96,7 +96,7 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -167,8 +167,8 @@ final class FuncCallbackImportTests { 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); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(callback, fn); } HANDLE.invokeExact(callback, fn); } catch (Throwable ex$) { @@ -190,7 +190,7 @@ final class FuncCallbackImportTests { /* _0: */SwiftValueLayout.SWIFT_POINTER, /* _1: */SwiftValueLayout.SWIFT_FLOAT ); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -206,7 +206,7 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -281,8 +281,8 @@ final class FuncCallbackImportTests { 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); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(body); } HANDLE.invokeExact(body); } catch (Throwable ex$) { @@ -304,7 +304,7 @@ final class FuncCallbackImportTests { /* _0: */SwiftValueLayout.SWIFT_POINTER, /* _1: */SwiftValueLayout.SWIFT_INT ); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 7af3c706..a714367e 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -66,8 +66,8 @@ final class FunctionDescriptorTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long i) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(i); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(i); } HANDLE.invokeExact(i); } catch (Throwable ex$) { @@ -102,8 +102,8 @@ final class FunctionDescriptorTests { private 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); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(l, i32); } HANDLE.invokeExact(l, i32); } catch (Throwable ex$) { @@ -138,8 +138,8 @@ final class FunctionDescriptorTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(long i) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(i); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(i); } return (long) HANDLE.invokeExact(i); } catch (Throwable ex$) { @@ -174,8 +174,8 @@ final class FunctionDescriptorTests { private 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); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(self); } return (int) HANDLE.invokeExact(self); } catch (Throwable ex$) { @@ -209,8 +209,8 @@ final class FunctionDescriptorTests { 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) { - SwiftKit.traceDowncall(newValue, self); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(newValue, self); } HANDLE.invokeExact(newValue, self); } catch (Throwable ex$) { diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 36610dcd..118ca789 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -180,7 +180,7 @@ final class MethodImportTests { */ public static void globalTakeIntLongString(int i32, long l, java.lang.String s) { try(var arena$ = Arena.ofConfined()) { - swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.call(i32, l, SwiftKit.toCString(s, arena$)); + swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.call(i32, l, SwiftRuntime.toCString(s, arena$)); } } """ @@ -222,7 +222,7 @@ final class MethodImportTests { * public func globalReturnClass() -> MySwiftClass * } */ - public static MySwiftClass globalReturnClass(SwiftArena swiftArena$) { + public static MySwiftClass globalReturnClass(AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_globalReturnClass.call(_result); return new MySwiftClass(_result, swiftArena$); @@ -396,7 +396,7 @@ final class MethodImportTests { * public init(len: Swift.Int, cap: Swift.Int) * } */ - public static MySwiftClass init(long len, long cap, SwiftArena swiftArena$) { + public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result) return new MySwiftClass(_result, swiftArena$); @@ -440,7 +440,7 @@ final class MethodImportTests { * public init(len: Swift.Int, cap: Swift.Int) * } */ - public static MySwiftStruct init(long len, long cap, SwiftArena swiftArena$) { + public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT); swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result) return new MySwiftStruct(_result, swiftArena$); diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index 99dfab5c..6a5c1c80 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -45,8 +45,8 @@ final class StringPassingTests { private 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); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(string); } return (long) HANDLE.invokeExact(string); } catch (Throwable ex$) { @@ -64,7 +64,7 @@ final class StringPassingTests { */ public static long writeString(java.lang.String string) { try(var arena$ = Arena.ofConfined()) { - return swiftjava___FakeModule_writeString_string.call(SwiftKit.toCString(string, arena$)); + return swiftjava___FakeModule_writeString_string.call(SwiftRuntime.toCString(string, arena$)); } } """ diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 33f41e20..e0a6678c 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -51,8 +51,8 @@ final class VariableImportTests { private 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); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(self); } return (long) HANDLE.invokeExact(self); } catch (Throwable ex$) { @@ -84,8 +84,8 @@ final class VariableImportTests { 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) { - SwiftKit.traceDowncall(newValue, self); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(newValue, self); } HANDLE.invokeExact(newValue, self); } catch (Throwable ex$) { diff --git a/settings.gradle b/settings.gradle index fa0fa5bd..9bf38ba5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,7 +18,8 @@ pluginManagement { rootProject.name = "swift-java" -include "SwiftKit" +include "SwiftKitCore" +include "SwiftKitFFM" // Include sample apps -- you can run them via `gradle Name:run` new File(rootDir, "Samples").listFiles().each { From d9699337581c6361188be9da27300eb8c50ba638 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 8 Jul 2025 11:03:20 -0700 Subject: [PATCH 088/178] [JExtract] Pass type metadata pointer to value witness functions Instead of the pointer to the value witness table. Witnesses expect type metadata. --- .../swiftkit/ffm/SwiftValueWitnessTable.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java index a0a6a79f..2b918523 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java @@ -185,8 +185,8 @@ private static class destroy { $LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("destroy")); static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - ValueLayout.ADDRESS, // witness table functions expect a pointer to self pointer - ValueLayout.ADDRESS // pointer to the witness table + ValueLayout.ADDRESS, // pointer to self + ValueLayout.ADDRESS // pointer to the type metadata ); /** @@ -213,13 +213,9 @@ static MethodHandle handle(SwiftAnyType ty) { * This includes deallocating the Swift managed memory for the object. */ public static void destroy(SwiftAnyType type, MemorySegment object) { - var fullTypeMetadata = fullTypeMetadata(type.$memorySegment()); - var wtable = valueWitnessTable(fullTypeMetadata); - var mh = destroy.handle(type); - try { - mh.invokeExact(object, wtable); + mh.invokeExact(object, type.$memorySegment()); } catch (Throwable th) { throw new AssertionError("Failed to destroy '" + type + "' at " + object, th); } @@ -246,7 +242,7 @@ private static class initializeWithCopy { /* -> */ ValueLayout.ADDRESS, // returns the destination object ValueLayout.ADDRESS, // destination ValueLayout.ADDRESS, // source - ValueLayout.ADDRESS // pointer to the witness table + ValueLayout.ADDRESS // pointer to the type metadata ); /** @@ -274,13 +270,10 @@ static MethodHandle handle(SwiftAnyType ty) { * Returns the dest object. */ public static MemorySegment initializeWithCopy(SwiftAnyType type, MemorySegment dest, MemorySegment src) { - var fullTypeMetadata = fullTypeMetadata(type.$memorySegment()); - var wtable = valueWitnessTable(fullTypeMetadata); - var mh = initializeWithCopy.handle(type); try { - return (MemorySegment) mh.invokeExact(dest, src, wtable); + return (MemorySegment) mh.invokeExact(dest, src, type.$memorySegment()); } catch (Throwable th) { throw new AssertionError("Failed to initializeWithCopy '" + type + "' (" + dest + ", " + src + ")", th); } From c2f683e0c890a6044fc47c6677dad4d12d06aaa5 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 7 Jul 2025 10:08:18 -0700 Subject: [PATCH 089/178] [JExtract] Import 'Foundation.Data' if used * Introduce `KnownSwiftModules` to represent `Swift` and `Foundation` modules. * `SwiftSymbolTable.setup()` is now a factory method to create `SwiftSymbolTable`. It scans the source file for module dependency. If `import Foundation` is found, add `Foundation` from the `KnownSwiftModules` (Just `Data` for now) * In `Swift2JavaTranslator` import `Data` APIs if any of the imported APIs use `Data` * `Data` has limited hardcoded APIs `init(bytes:count:)`, `getter:count`, and `withUnsafeBytes(_:)`. * Add `import Foundation` in Swift thunk if any of the the original source files import it --- .../JExtractSwiftPlugin.swift | 7 + .../MySwiftLibrary/MySwiftLibrary.swift | 6 + .../com/example/swift/HelloJava2Swift.java | 18 + .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 28 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 14 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 16 +- .../FFM/FFMSwift2JavaGenerator.swift | 5 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 8 +- .../JNI/JNISwift2JavaGenerator.swift | 3 + .../Swift2JavaTranslator.swift | 85 ++++- .../SwiftTypes/DependencyScanner.swift | 29 ++ .../SwiftTypes/SwiftKnownModules.swift | 94 +++++ .../SwiftTypes/SwiftKnownTypeDecls.swift | 61 ++++ .../SwiftTypes/SwiftKnownTypes.swift | 46 +-- .../SwiftNominalTypeDeclaration.swift | 19 +- .../SwiftParsedModuleSymbolTable.swift | 120 ------- .../SwiftParsedModuleSymbolTableBuilder.swift | 178 ++++++++++ .../SwiftStandardLibraryTypeDecls.swift | 198 ----------- .../SwiftTypes/SwiftSymbolTable.swift | 125 +++---- .../Asserts/LoweringAssertions.swift | 6 +- .../JExtractSwiftTests/DataImportTests.swift | 328 ++++++++++++++++++ .../SwiftSymbolTableTests.swift | 8 +- 23 files changed, 936 insertions(+), 470 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift delete mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift delete mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift create mode 100644 Tests/JExtractSwiftTests/DataImportTests.swift diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 7bfb51cf..83c5f0d9 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -100,6 +100,13 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { outputSwiftDirectory.appending(path: "\(sourceModule.name)Module+SwiftJava.swift") ] + // If the module uses 'Data' type, the thunk file is emitted as if 'Data' is declared + // in that module. Declare the thunk file as the output. + // FIXME: Make this conditional. + outputSwiftFiles += [ + outputSwiftDirectory.appending(path: "Data+SwiftJava.swift") + ] + return [ .buildCommand( displayName: "Generate Java wrappers for Swift types", diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 18b6546d..ce949283 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -23,6 +23,8 @@ import Glibc import Darwin.C #endif +import Foundation + public func helloWorld() { p("\(#function)") } @@ -53,6 +55,10 @@ public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1)) +public func globalReceiveReturnData(data: Data) -> Data { + return Data(data) +} + public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } 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 668140b2..985820b1 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -22,6 +22,10 @@ import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + public class HelloJava2Swift { public static void main(String[] args) { @@ -78,6 +82,20 @@ static void examples() { }); } + // Example of using 'Data'. + try (var arena = AllocatingSwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var origDat = Data.init(origBytes, origBytes.byteSize(), arena); + SwiftRuntime.trace("origDat.count = " + origDat.getCount()); + + var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); + retDat.withUnsafeBytes((retBytes) -> { + var str = retBytes.getString(0); + SwiftRuntime.trace("retStr=" + str); + }); + } + + System.out.println("DONE."); } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index eb27e5f6..9f4a9ac9 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -102,7 +102,7 @@ enum CDeclToCLoweringError: Error { case invalidFunctionConvention(SwiftFunctionType) } -extension SwiftStandardLibraryTypeKind { +extension SwiftKnownTypeDeclKind { /// Determine the primitive C type that corresponds to this C standard /// library type, if there is one. var primitiveCType: CType? { @@ -125,7 +125,7 @@ extension SwiftStandardLibraryTypeKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index db764e2e..488bd4e7 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -29,7 +29,7 @@ extension FFMSwift2JavaGenerator { enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } /// Lower the given initializer to a C-compatible entrypoint, @@ -46,7 +46,7 @@ extension FFMSwift2JavaGenerator { symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } /// Lower the given variable decl to a C-compatible entrypoint, @@ -69,7 +69,7 @@ extension FFMSwift2JavaGenerator { enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } } @@ -77,8 +77,8 @@ extension FFMSwift2JavaGenerator { struct CdeclLowering { var knownTypes: SwiftKnownTypes - init(swiftStdlibTypes: SwiftStandardLibraryTypeDecls) { - self.knownTypes = SwiftKnownTypes(decls: swiftStdlibTypes) + init(symbolTable: SwiftSymbolTable) { + self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) } /// Lower the given Swift function signature to a Swift @_cdecl function signature, @@ -266,6 +266,9 @@ struct CdeclLowering { ) } + case .data: + break + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) @@ -407,6 +410,9 @@ struct CdeclLowering { ]) ) + case .data: + break + default: throw LoweringError.unhandledType(type) } @@ -509,6 +515,9 @@ struct CdeclLowering { // Returning string is not supported at this point. throw LoweringError.unhandledType(type) + case .data: + break + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) @@ -663,8 +672,7 @@ extension LoweredFunctionSignature { package func cdeclThunk( cName: String, swiftAPIName: String, - as apiKind: SwiftAPIKind, - stdlibTypes: SwiftStandardLibraryTypeDecls + as apiKind: SwiftAPIKind ) -> FunctionDeclSyntax { let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ") @@ -765,7 +773,7 @@ extension LoweredFunctionSignature { } enum LoweringError: Error { - case inoutNotSupported(SwiftType) - case unhandledType(SwiftType) - case effectNotSupported(SwiftEffectSpecifier) + case inoutNotSupported(SwiftType, file: String = #file, line: Int = #line) + case unhandledType(SwiftType, file: String = #file, line: Int = #line) + case effectNotSupported(SwiftEffectSpecifier, file: String = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index d29f6b96..fe1806a9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes) + let translation = JavaTranslation(symbolTable: self.symbolTable) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -113,12 +113,12 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { - var swiftStdlibTypes: SwiftStandardLibraryTypeDecls + var symbolTable: SwiftSymbolTable func translate( _ decl: ImportedFunc ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) + let lowering = CdeclLowering(symbolTable: symbolTable) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. @@ -353,6 +353,9 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "SwiftRuntime.toCString", withArena: true) ) + case .data: + break + default: throw JavaTranslationError.unhandledType(swiftType) } @@ -438,6 +441,9 @@ extension FFMSwift2JavaGenerator { ) ) + case .data: + break + case .unsafePointer, .unsafeMutablePointer: // FIXME: Implement throw JavaTranslationError.unhandledType(swiftType) @@ -629,6 +635,6 @@ extension CType { } enum JavaTranslationError: Error { - case inoutNotSupported(SwiftType) + case inoutNotSupported(SwiftType, file: String = #file, line: Int = #line) 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 d6b09b24..d717255d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -87,6 +87,8 @@ extension FFMSwift2JavaGenerator { """) + printSwiftThunkImports(&printer) + for thunk in stt.renderGlobalThunks() { printer.print(thunk) printer.println() @@ -114,11 +116,22 @@ extension FFMSwift2JavaGenerator { """ ) + printSwiftThunkImports(&printer) + for thunk in stt.renderThunks(forType: ty) { printer.print("\(thunk)") printer.print("") } } + + func printSwiftThunkImports(_ printer: inout CodePrinter) { + for module in self.symbolTable.importedModules.keys.sorted() { + guard module != "Swift" else { + continue + } + printer.print("import \(module)") + } + } } struct SwiftThunkTranslator { @@ -196,8 +209,7 @@ struct SwiftThunkTranslator { let thunkFunc = translated.loweredSignature.cdeclThunk( cName: thunkName, swiftAPIName: decl.name, - as: decl.apiKind, - stdlibTypes: st.swiftStdlibTypes + as: decl.apiKind ) return [DeclSyntax(thunkFunc)] } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 9a140b94..6cf80d9b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -24,7 +24,6 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String - let swiftStdlibTypes: SwiftStandardLibraryTypeDecls let symbolTable: SwiftSymbolTable var javaPackagePath: String { @@ -53,7 +52,6 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.swiftOutputDirectory = swiftOutputDirectory 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 { @@ -65,6 +63,9 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + + // FIXME: Can we avoid this? + self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a5df702c..929a332c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -67,7 +67,7 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { - guard let javaType = translate(standardLibraryType: knownType) else { + guard let javaType = translate(knownType: knownType) else { fatalError("unsupported known type: \(knownType)") } return javaType @@ -83,8 +83,8 @@ extension JNISwift2JavaGenerator { } } - func translate(standardLibraryType: SwiftStandardLibraryTypeKind) -> JavaType? { - switch standardLibraryType { + func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { + switch knownType { case .bool: .boolean case .int8: .byte case .uint16: .char @@ -101,6 +101,8 @@ extension JNISwift2JavaGenerator { .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: nil + case .data: + fatalError("unimplemented") } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index b242cf66..0cc523b4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -58,6 +58,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + + // FIXME: Can we avoid this? + self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 89deda9a..82798c15 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -27,6 +27,9 @@ public final class Swift2JavaTranslator { let config: Configuration + /// The name of the Swift module being translated. + let swiftModuleName: String + // ==== Input struct Input { @@ -46,14 +49,7 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - package var swiftStdlibTypeDecls: SwiftStandardLibraryTypeDecls - - package let symbolTable: SwiftSymbolTable - - /// The name of the Swift module being translated. - var swiftModuleName: String { - symbolTable.moduleName - } + package var symbolTable: SwiftSymbolTable! = nil public init( config: Configuration @@ -62,12 +58,7 @@ public final class Swift2JavaTranslator { 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") - self.swiftStdlibTypeDecls = SwiftStandardLibraryTypeDecls(into: &parsedSwiftModule) - self.symbolTable.importedModules.append(parsedSwiftModule.symbolTable) + self.swiftModuleName = swiftModule } } @@ -108,11 +99,69 @@ extension Swift2JavaTranslator { log.trace("Analyzing \(input.filePath)") visitor.visit(sourceFile: input.syntax) } + + // If any API uses 'Foundation.Data', import 'Data' as if it's declared in + // this module. + if let dataDecl = self.symbolTable[.data] { + if self.isUsing(dataDecl) { + visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) + } + } } package func prepareForTranslation() { - /// Setup the symbol table. - symbolTable.setup(inputs.map({ $0.syntax })) + self.symbolTable = SwiftSymbolTable.setup( + moduleName: self.swiftModuleName, + inputs.map({ $0.syntax }), + log: self.log + ) + } + + /// Check if any of the imported decls uses the specified nominal declaration. + func isUsing(_ decl: SwiftNominalTypeDeclaration) -> Bool { + func check(_ type: SwiftType) -> Bool { + switch type { + case .nominal(let nominal): + return nominal.nominalTypeDecl == decl + case .optional(let ty): + return check(ty) + case .tuple(let tuple): + return tuple.contains(where: check) + case .function(let fn): + return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) }) + case .metatype(let ty): + return check(ty) + } + } + + func check(_ fn: ImportedFunc) -> Bool { + if check(fn.functionSignature.result.type) { + return true + } + if fn.functionSignature.parameters.contains(where: { check($0.type) }) { + return true + } + return false + } + + if self.importedGlobalFuncs.contains(where: check) { + return true + } + if self.importedGlobalVariables.contains(where: check) { + return true + } + for importedType in self.importedTypes.values { + if importedType.initializers.contains(where: check) { + return true + } + if importedType.methods.contains(where: check) { + return true + } + if importedType.variables.contains(where: check) { + return true + } + } + return false } } @@ -146,10 +195,10 @@ extension Swift2JavaTranslator { } // Whether to import this extension? - guard let nominalNode = symbolTable.parsedModule.nominalTypeSyntaxNodes[swiftNominalDecl] else { + guard swiftNominalDecl.moduleName == self.swiftModuleName else { return nil } - guard nominalNode.shouldImport(log: log) else { + guard swiftNominalDecl.syntax!.shouldImport(log: log) else { return nil } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift new file mode 100644 index 00000000..409c81a7 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.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 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Scan importing modules. +func importingModuleNames(sourceFile: SourceFileSyntax) -> [String] { + var importingModuleNames: [String] = [] + for item in sourceFile.statements { + if let importDecl = item.item.as(ImportDeclSyntax.self) { + guard let moduleName = importDecl.path.first?.name.text else { + continue + } + importingModuleNames.append(moduleName) + } + } + return importingModuleNames +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift new file mode 100644 index 00000000..21862c16 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -0,0 +1,94 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +enum SwiftKnownModule: String { + case swift = "Swift" + case foundation = "Foundation" + + var name: String { + return self.rawValue + } + + var symbolTable: SwiftModuleSymbolTable { + return switch self { + case .swift: swiftSymbolTable + case .foundation: foundationSymbolTable + } + } + + var sourceFile: SourceFileSyntax { + return switch self { + case .swift: swiftSourceFile + case .foundation: foundationSourceFile + } + } +} + +private var swiftSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Swift", importedModules: [:]) + builder.handle(sourceFile: swiftSourceFile) + return builder.finalize() +} + +private var foundationSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Foundation", importedModules: ["Swift": swiftSymbolTable]) + builder.handle(sourceFile: foundationSourceFile) + return builder.finalize() +} + +private let swiftSourceFile: SourceFileSyntax = """ + public struct Bool {} + public struct Int {} + public struct UInt {} + public struct Int8 {} + public struct UInt8 {} + public struct Int16 {} + public struct UInt16 {} + public struct Int32 {} + public struct UInt32 {} + public struct Int64 {} + public struct UInt64 {} + public struct Float {} + public struct Double {} + + public struct UnsafeRawPointer {} + public struct UnsafeMutableRawPointer {} + public struct UnsafeRawBufferPointer {} + public struct UnsafeMutableRawBufferPointer {} + + public struct UnsafePointer {} + public struct UnsafeMutablePointer {} + + public struct UnsafeBufferPointer {} + public struct UnsafeMutableBufferPointer {} + + // FIXME: Support 'typealias Void = ()' + public struct Void {} + + public struct String { + public init(cString: UnsafePointer) + public func withCString(_ body: (UnsafePointer) -> Void) + } + """ + +private let foundationSourceFile: SourceFileSyntax = """ + public struct Data { + public init(bytes: UnsafeRawPointer, count: Int) + public var count: Int { get } + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) + } + """ diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift new file mode 100644 index 00000000..0ac915c7 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +enum SwiftKnownTypeDeclKind: String, Hashable { + case bool = "Swift.Bool" + case int = "Swift.Int" + case uint = "Swift.UInt" + case int8 = "Swift.Int8" + case uint8 = "Swift.UInt8" + case int16 = "Swift.Int16" + case uint16 = "Swift.UInt16" + case int32 = "Swift.Int32" + case uint32 = "Swift.UInt32" + case int64 = "Swift.Int64" + case uint64 = "Swift.UInt64" + case float = "Swift.Float" + case double = "Swift.Double" + case unsafeRawPointer = "Swift.UnsafeRawPointer" + case unsafeMutableRawPointer = "Swift.UnsafeMutableRawPointer" + case unsafeRawBufferPointer = "Swift.UnsafeRawBufferPointer" + case unsafeMutableRawBufferPointer = "Swift.UnsafeMutableRawBufferPointer" + case unsafePointer = "Swift.UnsafePointer" + case unsafeMutablePointer = "Swift.UnsafeMutablePointer" + case unsafeBufferPointer = "Swift.UnsafeBufferPointer" + case unsafeMutableBufferPointer = "Swift.UnsafeMutableBufferPointer" + case void = "Swift.Void" + case string = "Swift.String" + + case data = "Foundation.Data" + + var moduleAndName: (module: String, name: String) { + let qualified = self.rawValue + let period = qualified.firstIndex(of: ".")! + return ( + module: String(qualified[.. SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafePointerDecl, + nominalTypeDecl: symbolTable[.unsafePointer], genericArguments: [pointeeType] ) ) @@ -47,7 +47,7 @@ struct SwiftKnownTypes { func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeMutablePointerDecl, + nominalTypeDecl: symbolTable[.unsafeMutablePointer], genericArguments: [pointeeType] ) ) @@ -56,7 +56,7 @@ struct SwiftKnownTypes { func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeBufferPointerDecl, + nominalTypeDecl: symbolTable[.unsafeBufferPointer], genericArguments: [elementType] ) ) @@ -65,7 +65,7 @@ struct SwiftKnownTypes { func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeMutableBufferPointerDecl, + nominalTypeDecl: symbolTable[.unsafeMutableBufferPointer], genericArguments: [elementType] ) ) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 29a287fe..b63f930c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -31,28 +31,28 @@ package class SwiftNominalTypeDeclaration { /// The syntax node this declaration is derived from. /// Can be `nil` if this is loaded from a .swiftmodule. - var syntax: NominalTypeDeclSyntaxNode? + let syntax: NominalTypeDeclSyntaxNode? /// The kind of nominal type. - var kind: Kind + let kind: Kind /// The parent nominal type when this nominal type is nested inside another type, e.g., /// MyCollection.Iterator. - var parent: SwiftNominalTypeDeclaration? + let 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 + let moduleName: String /// The name of this nominal type, e.g., 'MyCollection'. - var name: String + let name: String // TODO: Generic parameters. /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - lazy var knownStandardLibraryType: SwiftStandardLibraryTypeKind? = { + lazy var knownStandardLibraryType: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() @@ -66,6 +66,7 @@ package class SwiftNominalTypeDeclaration { self.moduleName = moduleName self.parent = parent self.name = node.name.text + self.syntax = node // Determine the kind from the syntax node. switch Syntax(node).as(SyntaxEnum.self) { @@ -80,12 +81,12 @@ package class SwiftNominalTypeDeclaration { /// Determine the known standard library type for this nominal type /// declaration. - private func computeKnownStandardLibraryType() -> SwiftStandardLibraryTypeKind? { - if parent != nil || moduleName != "Swift" { + private func computeKnownStandardLibraryType() -> SwiftKnownTypeDeclKind? { + if parent != nil { return nil } - return SwiftStandardLibraryTypeKind(typeNameInSwiftModule: name) + return SwiftKnownTypeDeclKind(rawValue: "\(moduleName).\(name)") } package var qualifiedName: String { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift deleted file mode 100644 index 1eb37eac..00000000 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift +++ /dev/null @@ -1,120 +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 -// -//===----------------------------------------------------------------------===// - -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/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift new file mode 100644 index 00000000..4026e624 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -0,0 +1,178 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftParsedModuleSymbolTableBuilder { + let log: Logger? + + /// The symbol table being built. + var symbolTable: SwiftModuleSymbolTable + + /// Imported modules to resolve type syntax. + let importedModules: [String: SwiftModuleSymbolTable] + + /// Extension decls their extended type hasn't been resolved. + var unresolvedExtensions: [ExtensionDeclSyntax] + + init(moduleName: String, importedModules: [String: SwiftModuleSymbolTable], log: Logger? = nil) { + self.log = log + self.symbolTable = .init(moduleName: moduleName) + self.importedModules = importedModules + self.unresolvedExtensions = [] + } + + var moduleName: String { + symbolTable.moduleName + } +} + +extension SwiftParsedModuleSymbolTableBuilder { + + mutating func handle( + sourceFile: SourceFileSyntax + ) { + // Find top-level type declarations. + for statement in sourceFile.statements { + // We only care about declarations. + guard case .decl(let decl) = statement.item else { + continue + } + + if let nominalTypeNode = decl.asNominal { + self.handle(nominalTypeDecl: nominalTypeNode, parent: nil) + } + if let extensionNode = decl.as(ExtensionDeclSyntax.self) { + self.handle(extensionDecl: extensionNode) + } + } + } + + /// Add a nominal type declaration and all of the nested types within it to the symbol + /// table. + mutating func handle( + nominalTypeDecl node: NominalTypeDeclSyntaxNode, + parent: SwiftNominalTypeDeclaration? + ) { + // If we have already recorded a nominal type with the name in this module, + // it's an invalid redeclaration. + if let _ = symbolTable.lookupType(node.name.text, parent: parent) { + log?.debug("Failed to add a decl into symbol table: redeclaration; " + node.nameForDebug) + return + } + + // Otherwise, create the nominal type declaration. + let nominalTypeDecl = SwiftNominalTypeDeclaration( + moduleName: moduleName, + parent: parent, + node: node + ) + + if let parent { + // For nested types, make them discoverable from the parent type. + symbolTable.nestedTypes[parent, default: [:]][nominalTypeDecl.name] = nominalTypeDecl + } else { + // For top-level types, make them discoverable by name. + symbolTable.topLevelTypes[nominalTypeDecl.name] = nominalTypeDecl + } + + self.handle(memberBlock: node.memberBlock, parent: nominalTypeDecl) + } + + mutating func handle( + memberBlock node: MemberBlockSyntax, + parent: SwiftNominalTypeDeclaration + ) { + for member in node.members { + // Find any nested types within this nominal type and add them. + if let nominalMember = member.decl.asNominal { + self.handle(nominalTypeDecl: nominalMember, parent: parent) + } + } + + } + + mutating func handle( + extensionDecl node: ExtensionDeclSyntax + ) { + if !self.tryHandle(extension: node) { + self.unresolvedExtensions.append(node) + } + } + + /// Add any nested types within the given extension to the symbol table. + /// If the extended nominal type can't be resolved, returns false. + mutating func tryHandle( + extension node: ExtensionDeclSyntax + ) -> Bool { + // Try to resolve the type referenced by this extension declaration. + // If it fails, we'll try again later. + let table = SwiftSymbolTable( + parsedModule: symbolTable, + importedModules: importedModules + ) + guard let extendedType = try? SwiftType(node.extendedType, symbolTable: table) else { + return false + } + guard let extendedNominal = extendedType.asNominalTypeDeclaration else { + // Extending type was not a nominal type. Ignore it. + return true + } + + // Find any nested types within this extension and add them. + self.handle(memberBlock: node.memberBlock, parent: extendedNominal) + return true + } + + /// Finalize the symbol table and return it. + mutating func finalize() -> SwiftModuleSymbolTable { + // Handle the unresolved 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 {} + // + while !unresolvedExtensions.isEmpty { + var extensions = self.unresolvedExtensions + extensions.removeAll(where: { + self.tryHandle(extension: $0) + }) + + // If we didn't resolve anything, we're done. + if extensions.count == unresolvedExtensions.count { + break + } + + assert(extensions.count < unresolvedExtensions.count) + self.unresolvedExtensions = extensions + } + + return symbolTable + } +} + +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/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift deleted file mode 100644 index 5678b2bb..00000000 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift +++ /dev/null @@ -1,198 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -enum SwiftStandardLibraryTypeKind: 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 unsafeRawBufferPointer = "UnsafeRawBufferPointer" - case unsafeMutableRawBufferPointer = "UnsafeMutableRawBufferPointer" - case unsafePointer = "UnsafePointer" - case unsafeMutablePointer = "UnsafeMutablePointer" - case unsafeBufferPointer = "UnsafeBufferPointer" - case unsafeMutableBufferPointer = "UnsafeMutableBufferPointer" - case string = "String" - case void = "Void" - - var typeName: String { rawValue } - - init?(typeNameInSwiftModule: String) { - self.init(rawValue: typeNameInSwiftModule) - } - - /// Whether this declaration is generic. - var isGeneric: Bool { - switch self { - case .bool, .double, .float, .int, .int8, .int16, .int32, .int64, - .uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer, - .unsafeMutableRawPointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .string, .void: - false - - case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, - .unsafeMutableBufferPointer: - 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 -/// forms, so that the translator can reason about them in source code. -public struct SwiftStandardLibraryTypeDecls { - // Swift.UnsafePointer - let unsafePointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeMutablePointer - let unsafeMutablePointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeBufferPointer - let unsafeBufferPointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeMutableBufferPointer - let unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration - - /// Mapping from known standard library types to their nominal type declaration. - let knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] - - /// Mapping from nominal type declarations to known types. - let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] - - private static func recordKnownType( - _ type: SwiftStandardLibraryTypeKind, - _ syntax: NominalTypeDeclSyntaxNode, - knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], - parsedModule: inout SwiftParsedModuleSymbolTable - ) { - let nominalDecl = parsedModule.addNominalTypeDeclaration(syntax, parent: nil) - knownTypeToNominal[type] = nominalDecl - nominalTypeDeclToKnownType[nominalDecl] = type - } - - private static func recordKnownNonGenericStruct( - _ type: SwiftStandardLibraryTypeKind, - knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], - parsedModule: inout SwiftParsedModuleSymbolTable - ) { - recordKnownType( - type, - StructDeclSyntax( - name: .identifier(type.typeName), - memberBlock: .init(members: []) - ), - knownTypeToNominal: &knownTypeToNominal, - nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, - parsedModule: &parsedModule - ) - } - - init(into parsedModule: inout SwiftParsedModuleSymbolTable) { - // Pointer types - 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 - ) - - var knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] = [:] - var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] = [:] - - // Handle all of the non-generic types at once. - for knownType in SwiftStandardLibraryTypeKind.allCases { - guard !knownType.isGeneric else { - continue - } - - Self.recordKnownNonGenericStruct( - knownType, - knownTypeToNominal: &knownTypeToNominal, - nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, - parsedModule: &parsedModule - ) - } - - self.knownTypeToNominal = knownTypeToNominal - self.nominalTypeDeclToKnownType = nominalTypeDeclToKnownType - } - - subscript(knownType: SwiftStandardLibraryTypeKind) -> SwiftNominalTypeDeclaration { - knownTypeToNominal[knownType]! - } - - subscript(nominalType: SwiftNominalTypeDeclaration) -> SwiftStandardLibraryTypeKind? { - nominalTypeDeclToKnownType[nominalType] - } -} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 87c2c80f..239d38c4 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -38,89 +38,50 @@ extension SwiftSymbolTableProtocol { } package class SwiftSymbolTable { - var importedModules: [SwiftModuleSymbolTable] = [] - var parsedModule: SwiftParsedModuleSymbolTable + let importedModules: [String: SwiftModuleSymbolTable] + let parsedModule:SwiftModuleSymbolTable - package init(parsedModuleName: String) { - self.parsedModule = SwiftParsedModuleSymbolTable(moduleName: parsedModuleName) - } + private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] - func addImportedModule(symbolTable: SwiftModuleSymbolTable) { - importedModules.append(symbolTable) + init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) { + self.parsedModule = parsedModule + self.importedModules = importedModules } } extension SwiftSymbolTable { - package func setup(_ sourceFiles: some Collection) { - // First, register top-level and nested nominal types to the symbol table. + package static func setup( + moduleName: String, + _ sourceFiles: some Collection, + log: Logger + ) -> SwiftSymbolTable { + + // Prepare imported modules. + // FIXME: Support arbitrary dependencies. + var moduleNames: Set = [] 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) - } - } - } + moduleNames.formUnion(importingModuleNames(sourceFile: sourceFile)) } - - 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 + var importedModules: [String: SwiftModuleSymbolTable] = [:] + importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable + for moduleName in moduleNames.sorted() { + if + importedModules[moduleName] == nil, + let knownModule = SwiftKnownModule(rawValue: moduleName) + { + importedModules[moduleName] = knownModule.symbolTable } - assert(numExtensionsBefore > unresolvedExtensions.count) } - } - private func addNominalTypeDeclarations(_ 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) - } - } + // FIXME: Support granular lookup context (file, type context). - 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 + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: moduleName, importedModules: importedModules, log: log) + // First, register top-level and nested nominal types to the symbol table. + for sourceFile in sourceFiles { + builder.handle(sourceFile: sourceFile) } - - // 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 + let parsedModule = builder.finalize() + return SwiftSymbolTable(parsedModule: parsedModule, importedModules: importedModules) } } @@ -134,12 +95,14 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules { + for importedModule in importedModules.values { if let result = importedModule.lookupTopLevelNominalType(name) { return result } } + // FIXME: Implement module qualified name lookups. E.g. 'Swift.String' + return nil } @@ -149,7 +112,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules { + for importedModule in importedModules.values { if let result = importedModule.lookupNestedType(name, parent: parent) { return result } @@ -158,3 +121,21 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return nil } } + +extension SwiftSymbolTable { + /// Map 'SwiftKnownTypeDeclKind' to the declaration. + subscript(knownType: SwiftKnownTypeDeclKind) -> SwiftNominalTypeDeclaration! { + if let known = knownTypeToNominal[knownType] { + return known + } + + let (module, name) = knownType.moduleAndName + guard let moduleTable = importedModules[module] else { + return nil + } + + let found = moduleTable.lookupTopLevelNominalType(name) + knownTypeToNominal[knownType] = found + return found + } +} diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index 05323630..eebfdf4a 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -73,8 +73,7 @@ func assertLoweredFunction( let loweredCDecl = loweredFunction.cdeclThunk( cName: "c_\(swiftFunctionName)", swiftAPIName: swiftFunctionName, - as: apiKind, - stdlibTypes: translator.swiftStdlibTypeDecls + as: apiKind ) #expect( @@ -141,8 +140,7 @@ func assertLoweredVariableAccessor( let loweredCDecl = loweredFunction?.cdeclThunk( cName: "c_\(swiftVariableName)", swiftAPIName: swiftVariableName, - as: isSet ? .setter : .getter, - stdlibTypes: translator.swiftStdlibTypeDecls + as: isSet ? .setter : .getter ) #expect( diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift new file mode 100644 index 00000000..75613407 --- /dev/null +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -0,0 +1,328 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import Testing + +final class DataImportTests { + let class_interfaceFile = + """ + import Foundation + + public func receiveData(dat: Data) + public func returnData() -> Data + """ + + @Test("Import Data: Swift thunks") + func swiftThunk() throws { + + try assertOutput( + input: class_interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + import Foundation + """, + """ + @_cdecl("swiftjava_SwiftModule_receiveData_dat") + public func swiftjava_SwiftModule_receiveData_dat(_ dat: UnsafeRawPointer) { + receiveData(dat: dat.assumingMemoryBound(to: Data.self).pointee) + } + """, + """ + @_cdecl("swiftjava_SwiftModule_returnData") + public func swiftjava_SwiftModule_returnData(_ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Data.self).initialize(to: returnData()) + } + """, + + """ + @_cdecl("swiftjava_getType_SwiftModule_Data") + public func swiftjava_getType_SwiftModule_Data() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(Data.self, to: UnsafeMutableRawPointer.self) + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_init_bytes_count") + public func swiftjava_SwiftModule_Data_init_bytes_count(_ bytes: UnsafeRawPointer, _ count: Int, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Data.self).initialize(to: Data(bytes: bytes, count: count)) + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_count$get") + public func swiftjava_SwiftModule_Data_count$get(_ self: UnsafeRawPointer) -> Int { + return self.assumingMemoryBound(to: Data.self).pointee.count + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_withUnsafeBytes__") + public func swiftjava_SwiftModule_Data_withUnsafeBytes__(_ body: @convention(c) (UnsafeRawPointer?, Int) -> Void, _ self: UnsafeRawPointer) { + self.assumingMemoryBound(to: Data.self).pointee.withUnsafeBytes({ (_0) in + return body(_0.baseAddress, _0.count) + }) + } + """, + ] + ) + } + + @Test("Import Data: JavaBindings") + func javaBindings() throws { + + try assertOutput( + input: class_interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveData_dat(const void *dat) + * } + */ + private static class swiftjava_SwiftModule_receiveData_dat { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* dat: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveData_dat"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment dat) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(dat); + } + HANDLE.invokeExact(dat); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveData(dat: Data) + * } + */ + public static void receiveData(Data dat) { + swiftjava_SwiftModule_receiveData_dat.call(dat.$memorySegment()); + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_returnData(void *_result) + * } + */ + private static class swiftjava_SwiftModule_returnData { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _result: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_returnData"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment _result) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(_result); + } + HANDLE.invokeExact(_result); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnData() -> Data + * } + */ + public static Data returnData(AllocatingSwiftArena swiftArena$) { + MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); + swiftjava_SwiftModule_returnData.call(_result); + return new Data(_result, swiftArena$); + } + """, + + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_Data_init_bytes_count(const void *bytes, ptrdiff_t count, void *_result) + * } + */ + private static class swiftjava_SwiftModule_Data_init_bytes_count { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* bytes: */SwiftValueLayout.SWIFT_POINTER, + /* count: */SwiftValueLayout.SWIFT_INT, + /* _result: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_init_bytes_count"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment bytes, long count, java.lang.foreign.MemorySegment _result) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(bytes, count, _result); + } + HANDLE.invokeExact(bytes, count, _result); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(bytes: UnsafeRawPointer, count: Int) + * } + */ + public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena$) { + MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); + swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result); + return new Data(_result, swiftArena$); + } + """, + + """ + /** + * {@snippet lang=c : + * ptrdiff_t swiftjava_SwiftModule_Data_count$get(const void *self) + * } + */ + private static class swiftjava_SwiftModule_Data_count$get { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_INT, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_count$get"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static long call(java.lang.foreign.MemorySegment self) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(self); + } + return (long) HANDLE.invokeExact(self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var count: Int + * } + */ + public long getCount() { + $ensureAlive(); + return swiftjava_SwiftModule_Data_count$get.call(this.$memorySegment()); + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_Data_withUnsafeBytes__(void (*body)(const void *, ptrdiff_t), const void *self) + * } + */ + private static class swiftjava_SwiftModule_Data_withUnsafeBytes__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* body: */SwiftValueLayout.SWIFT_POINTER, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_withUnsafeBytes__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment body, java.lang.foreign.MemorySegment self) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(body, self); + } + HANDLE.invokeExact(body, self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * {snippet lang=c : + * void (*)(const void *, ptrdiff_t) + * } + */ + private static class $body { + @FunctionalInterface + public interface Function { + void apply(java.lang.foreign.MemorySegment _0, long _1); + } + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _0: */SwiftValueLayout.SWIFT_POINTER, + /* _1: */SwiftValueLayout.SWIFT_INT + ); + private static final MethodHandle HANDLE = SwiftRuntime.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 withUnsafeBytes { + @FunctionalInterface + public interface body { + void apply(java.lang.foreign.MemorySegment _0); + } + private static MemorySegment $toUpcallStub(body fi, Arena arena) { + return swiftjava_SwiftModule_Data_withUnsafeBytes__.$body.toUpcallStub((_0_pointer, _0_count) -> { + fi.apply(_0_pointer.reinterpret(_0_count)); + }, arena); + } + } + """, + + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) + * } + */ + public void withUnsafeBytes(withUnsafeBytes.body body) { + $ensureAlive(); + try(var arena$ = Arena.ofConfined()) { + swiftjava_SwiftModule_Data_withUnsafeBytes__.call(withUnsafeBytes.$toUpcallStub(body, arena$), this.$memorySegment()); + } + } + """ + ] + ) + } + +} diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index 2bbaa913..fdbf2d5f 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -21,7 +21,6 @@ import Testing struct SwiftSymbolTableSuite { @Test func lookupBindingTests() throws { - let symbolTable = SwiftSymbolTable(parsedModuleName: "MyModule") let sourceFile1: SourceFileSyntax = """ extension X.Y { struct Z { } @@ -33,8 +32,11 @@ struct SwiftSymbolTableSuite { let sourceFile2: SourceFileSyntax = """ struct X {} """ - - symbolTable.setup([sourceFile1, sourceFile2]) + let symbolTable = SwiftSymbolTable.setup( + moduleName: "MyModule", + [sourceFile1, sourceFile2], + log: Logger(label: "swift-java", logLevel: .critical) + ) let x = try #require(symbolTable.lookupType("X", parent: nil)) let xy = try #require(symbolTable.lookupType("Y", parent: x)) From 21cff45bc012712bf93113ebc2eff791ede77ac2 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Thu, 10 Jul 2025 15:17:57 -0700 Subject: [PATCH 090/178] [JExtract/FFM] Translate 'some DataProtocol' parameters to 'Data' * Introduce `SwiftType.opaque(SwiftType)` and `.existential(SwiftType)` to represent `some` and `any` types respectively. * Introduce `SwiftKnownTypes.representativeType(of:)` to the hardcoded `DataProtocol` -> `Data` translation mapping. --- .../MySwiftLibrary/MySwiftLibrary.swift | 4 + .../com/example/swift/HelloJava2Swift.java | 10 +- .../FFM/CDeclLowering/CRepresentation.swift | 6 +- ...Swift2JavaGenerator+FunctionLowering.swift | 26 +++++- ...MSwift2JavaGenerator+JavaTranslation.swift | 66 +++++++++---- ...ISwift2JavaGenerator+JavaTranslation.swift | 8 +- .../Swift2JavaTranslator.swift | 12 ++- .../SwiftTypes/SwiftKnownModules.swift | 4 +- .../SwiftTypes/SwiftKnownTypeDecls.swift | 1 + .../SwiftTypes/SwiftKnownTypes.swift | 12 +++ .../SwiftNominalTypeDeclaration.swift | 2 +- .../SwiftTypes/SwiftType.swift | 37 ++++++-- .../JExtractSwiftTests/DataImportTests.swift | 92 ++++++++++++++++++- 13 files changed, 225 insertions(+), 55 deletions(-) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index ce949283..c9291c80 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -63,6 +63,10 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } +public func globalReceiveSomeDataProtocol(data: some DataProtocol) { + p(Array(data).description) +} + // ==== 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 985820b1..821b228e 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -22,10 +22,6 @@ import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; - public class HelloJava2Swift { public static void main(String[] args) { @@ -95,6 +91,12 @@ static void examples() { }); } + try (var arena = AllocatingSwiftArena.ofConfined()) { + var bytes = arena.allocateFrom("hello"); + var dat = Data.init(bytes, bytes.byteSize(), arena); + MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + } + System.out.println("DONE."); } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 9f4a9ac9..4e44e74b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -24,7 +24,7 @@ extension CType { init(cdeclType: SwiftType) throws { switch cdeclType { case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { if let primitiveCType = knownType.primitiveCType { self = primitiveCType return @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .metatype, .optional, .tuple: + case .metatype, .optional, .tuple, .opaque, .existential: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -125,7 +125,7 @@ extension SwiftKnownTypeDeclKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 488bd4e7..0c6c707c 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -77,6 +77,10 @@ extension FFMSwift2JavaGenerator { struct CdeclLowering { var knownTypes: SwiftKnownTypes + init(knownTypes: SwiftKnownTypes) { + self.knownTypes = knownTypes + } + init(symbolTable: SwiftSymbolTable) { self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) } @@ -165,7 +169,7 @@ struct CdeclLowering { ) case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { if convention == .inout { // FIXME: Support non-trivial 'inout' for builtin types. throw LoweringError.inoutNotSupported(type) @@ -320,6 +324,18 @@ struct CdeclLowering { conversion: conversion ) + case .opaque(let proto), .existential(let proto): + // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` + // Translate it as the concrete type. + // NOTE: This is a temporary workaround until we add support for generics. + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName) + } + throw LoweringError.unhandledType(type) + case .optional: throw LoweringError.unhandledType(type) } @@ -386,7 +402,7 @@ struct CdeclLowering { switch type { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: // pointer buffers are lowered to (raw-pointer, count) pair. @@ -421,7 +437,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .function, .metatype, .optional, .tuple: + case .function, .metatype, .optional, .tuple, .existential, .opaque: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -454,7 +470,7 @@ struct CdeclLowering { case .nominal(let nominal): // Types from the Swift standard library that we know about. - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafePointer, .unsafeMutablePointer: // Typed pointers are lowered to corresponding raw forms. @@ -575,7 +591,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .function(_), .optional(_): + case .function, .optional, .existential, .opaque: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index fe1806a9..db706a91 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -113,12 +113,16 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { - var symbolTable: SwiftSymbolTable + var knownTypes: SwiftKnownTypes + + init(symbolTable: SwiftSymbolTable) { + self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) + } func translate( _ decl: ImportedFunc ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(symbolTable: symbolTable) + let lowering = CdeclLowering(knownTypes: knownTypes) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. @@ -208,7 +212,7 @@ extension FFMSwift2JavaGenerator { switch type { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( @@ -248,11 +252,12 @@ extension FFMSwift2JavaGenerator { // 'self' let selfParameter: TranslatedParameter? if case .instance(let swiftSelf) = swiftSignature.selfParameter { - selfParameter = try self.translate( - swiftParam: swiftSelf, + selfParameter = try self.translateParameter( + type: swiftSelf.type, + convention: swiftSelf.convention, + parameterName: swiftSelf.parameterName ?? "self", loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName, - parameterName: swiftSelf.parameterName ?? "self" + methodName: methodName ) } else { selfParameter = nil @@ -263,11 +268,12 @@ extension FFMSwift2JavaGenerator { .map { (idx, swiftParam) in let loweredParam = loweredFunctionSignature.parameters[idx] let parameterName = swiftParam.parameterName ?? "_\(idx)" - return try self.translate( - swiftParam: swiftParam, + return try self.translateParameter( + type: swiftParam.type, + convention: swiftParam.convention, + parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName, - parameterName: parameterName + methodName: methodName ) } @@ -285,13 +291,13 @@ extension FFMSwift2JavaGenerator { } /// Translate a Swift API parameter to the user-facing Java API parameter. - func translate( - swiftParam: SwiftParameter, + func translateParameter( + type swiftType: SwiftType, + convention: SwiftParameterConvention, + parameterName: String, loweredParam: LoweredParameter, - methodName: String, - parameterName: String + methodName: 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. @@ -319,8 +325,8 @@ extension FFMSwift2JavaGenerator { ) case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { - if swiftParam.convention == .inout { + if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind { + if convention == .inout { // FIXME: Support non-trivial 'inout' for builtin types. throw JavaTranslationError.inoutNotSupported(swiftType) } @@ -388,6 +394,26 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) + case .existential(let proto), .opaque(let proto): + // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` + // Translate it as the concrete type. + // NOTE: This is a temporary workaround until we add support for generics. + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try translateParameter( + type: concreteTy, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName + ) + } + + // Otherwise, not supported yet. + throw JavaTranslationError.unhandledType(swiftType) + case .optional: throw JavaTranslationError.unhandledType(swiftType) } @@ -422,7 +448,7 @@ extension FFMSwift2JavaGenerator { ) case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedResult( @@ -476,7 +502,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .optional, .function: + case .optional, .function, .existential, .opaque: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 929a332c..f006b4a8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -66,7 +66,7 @@ extension JNISwift2JavaGenerator { func translate(swiftType: SwiftType) -> JavaType { switch swiftType { case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = translate(knownType: knownType) else { fatalError("unsupported known type: \(knownType)") } @@ -78,7 +78,7 @@ extension JNISwift2JavaGenerator { case .tuple([]): return .void - case .metatype, .optional, .tuple, .function: + case .metatype, .optional, .tuple, .function, .existential, .opaque: fatalError("unsupported type: \(self)") } } @@ -99,10 +99,8 @@ extension JNISwift2JavaGenerator { .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: + .unsafeBufferPointer, .unsafeMutableBufferPointer, .data, .dataProtocol: nil - case .data: - fatalError("unimplemented") } } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 82798c15..07023b09 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -103,7 +103,8 @@ extension Swift2JavaTranslator { // If any API uses 'Foundation.Data', import 'Data' as if it's declared in // this module. if let dataDecl = self.symbolTable[.data] { - if self.isUsing(dataDecl) { + let dataProtocolDecl = self.symbolTable[.dataProtocol]! + if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) } } @@ -117,12 +118,13 @@ extension Swift2JavaTranslator { ) } - /// Check if any of the imported decls uses the specified nominal declaration. - func isUsing(_ decl: SwiftNominalTypeDeclaration) -> Bool { + /// Check if any of the imported decls uses a nominal declaration that satisfies + /// the given predicate. + func isUsing(where predicate: (SwiftNominalTypeDeclaration) -> Bool) -> Bool { func check(_ type: SwiftType) -> Bool { switch type { case .nominal(let nominal): - return nominal.nominalTypeDecl == decl + return predicate(nominal.nominalTypeDecl) case .optional(let ty): return check(ty) case .tuple(let tuple): @@ -131,6 +133,8 @@ extension Swift2JavaTranslator { return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) }) case .metatype(let ty): return check(ty) + case .existential(let ty), .opaque(let ty): + return check(ty) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 21862c16..47abb7f0 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -86,7 +86,9 @@ private let swiftSourceFile: SourceFileSyntax = """ """ private let foundationSourceFile: SourceFileSyntax = """ - public struct Data { + public protocol DataProtocol {} + + public struct Data: DataProtocol { public init(bytes: UnsafeRawPointer, count: Int) public var count: Int { get } public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 0ac915c7..d6af5920 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -39,6 +39,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case void = "Swift.Void" case string = "Swift.String" + case dataProtocol = "Foundation.DataProtocol" case data = "Foundation.Data" var moduleAndName: (module: String, name: String) { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index ea4371a3..8c70f7e2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -35,6 +35,9 @@ struct SwiftKnownTypes { var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } + var dataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.dataProtocol])) } + var data: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.data])) } + func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( @@ -70,4 +73,13 @@ struct SwiftKnownTypes { ) ) } + + /// Returns the known representative concrete type if there is one for the + /// given protocol kind. E.g. `String` for `StringProtocol` + func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { + switch knownProtocol { + case .dataProtocol: return self.data + default: return nil + } + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index b63f930c..8be645c8 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: SwiftKnownTypeDeclKind? = { + lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index c2ddd2a5..531ab45c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -16,12 +16,25 @@ import SwiftSyntax /// Describes a type in the Swift type system. enum SwiftType: Equatable { + case nominal(SwiftNominalType) + indirect case function(SwiftFunctionType) + + /// `.Type` indirect case metatype(SwiftType) - case nominal(SwiftNominalType) + + /// `?` indirect case optional(SwiftType) + + /// `(, )` case tuple([SwiftType]) + /// `any ` + indirect case existential(SwiftType) + + /// `some ` + indirect case opaque(SwiftType) + static var void: Self { return .tuple([]) } @@ -30,7 +43,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .function, .metatype, .optional: nil + case .function, .metatype, .optional, .existential, .opaque: nil } } @@ -54,7 +67,7 @@ enum SwiftType: Equatable { var isPointer: Bool { switch self { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { return knownType.isPointer } default: @@ -73,7 +86,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .optional, .tuple: + case .optional, .tuple, .existential, .opaque: return false } } @@ -84,7 +97,7 @@ extension SwiftType: CustomStringConvertible { /// requires parentheses. private var postfixRequiresParentheses: Bool { switch self { - case .function: true + case .function, .existential, .opaque: true case .metatype, .nominal, .optional, .tuple: false } } @@ -103,6 +116,10 @@ extension SwiftType: CustomStringConvertible { return "\(wrappedType.description)?" case .tuple(let elements): return "(\(elements.map(\.description).joined(separator: ", ")))" + case .existential(let constraintType): + return "any \(constraintType)" + case .opaque(let constraintType): + return "some \(constraintType)" } } } @@ -159,8 +176,7 @@ extension SwiftType { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .compositionType, .dictionaryType, .missingType, .namedOpaqueReturnType, - .packElementType, .packExpansionType, .someOrAnyType, - .suppressedType: + .packElementType, .packExpansionType, .suppressedType: throw TypeTranslationError.unimplementedType(type) case .attributedType(let attributedType): @@ -254,6 +270,13 @@ extension SwiftType { self = try .tuple(tupleType.elements.map { element in try SwiftType(element.type, symbolTable: symbolTable) }) + + case .someOrAnyType(let someOrAntType): + if someOrAntType.someOrAnySpecifier.tokenKind == .keyword(.some) { + self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + } else { + self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + } } } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index 75613407..104f7f0e 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -16,7 +16,7 @@ import JExtractSwiftLib import Testing final class DataImportTests { - let class_interfaceFile = + let data_interfaceFile = """ import Foundation @@ -24,11 +24,19 @@ final class DataImportTests { public func returnData() -> Data """ + let dataProtocol_interfaceFile = + """ + import Foundation + + public func receiveDataProtocol(dat: some DataProtocol) + """ + + @Test("Import Data: Swift thunks") - func swiftThunk() throws { + func data_swiftThunk() throws { try assertOutput( - input: class_interfaceFile, .ffm, .swift, + input: data_interfaceFile, .ffm, .swift, expectedChunks: [ """ import Foundation @@ -80,10 +88,10 @@ final class DataImportTests { } @Test("Import Data: JavaBindings") - func javaBindings() throws { + func data_javaBindings() throws { try assertOutput( - input: class_interfaceFile, .ffm, .java, + input: data_interfaceFile, .ffm, .java, expectedChunks: [ """ /** @@ -325,4 +333,78 @@ final class DataImportTests { ) } + @Test("Import DataProtocol: Swift thunks") + func dataProtocol_swiftThunk() throws { + try assertOutput( + input: dataProtocol_interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + import Foundation + """, + """ + @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat") + public func swiftjava_SwiftModule_receiveDataProtocol_dat(_ dat: UnsafeRawPointer) { + receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee) + } + """, + + // Just to make sure 'Data' is imported. + """ + @_cdecl("swiftjava_getType_SwiftModule_Data") + """ + ] + ) + } + + @Test("Import DataProtocol: JavaBindings") + func dataProtocol_javaBindings() throws { + + try assertOutput( + input: dataProtocol_interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveDataProtocol_dat(const void *dat) + * } + */ + private static class swiftjava_SwiftModule_receiveDataProtocol_dat { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* dat: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment dat) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(dat); + } + HANDLE.invokeExact(dat); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveDataProtocol(dat: some DataProtocol) + * } + */ + public static void receiveDataProtocol(Data dat) { + swiftjava_SwiftModule_receiveDataProtocol_dat.call(dat.$memorySegment()); + } + """, + + // Just to make sure 'Data' is imported. + """ + public final class Data + """ + ] + ) + } } From 36d05c76f352b076ff1552c704b8d24cacae35d6 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Thu, 10 Jul 2025 16:30:29 -0700 Subject: [PATCH 091/178] [JExtract/FFM] Add Data and DataProtocol tests Also, run Samples:SwiftKitSampleApp:test in CI --- .../MySwiftLibrary/MySwiftLibrary.swift | 3 +- Samples/SwiftKitSampleApp/ci-validate.sh | 3 +- .../com/example/swift/DataImportTest.java | 49 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index c9291c80..1bcca4fd 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -63,8 +63,9 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } -public func globalReceiveSomeDataProtocol(data: some DataProtocol) { +public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int { p(Array(data).description) + return data.count } // ==== Internal helpers diff --git a/Samples/SwiftKitSampleApp/ci-validate.sh b/Samples/SwiftKitSampleApp/ci-validate.sh index 07b42627..c7a68d22 100755 --- a/Samples/SwiftKitSampleApp/ci-validate.sh +++ b/Samples/SwiftKitSampleApp/ci-validate.sh @@ -3,4 +3,5 @@ set -x set -e -./gradlew run \ No newline at end of file +./gradlew run +./gradlew test \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java new file mode 100644 index 00000000..52a63f81 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class DataImportTest { + @Test + void test_Data_receiveAndReturn() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var origDat = Data.init(origBytes, origBytes.byteSize(), arena); + assertEquals(7, origDat.getCount()); + + var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); + assertEquals(7, retDat.getCount()); + retDat.withUnsafeBytes((retBytes) -> { + assertEquals(7, retBytes.byteSize()); + var str = retBytes.getString(0); + assertEquals("foobar", str); + }); + } + } + + @Test + void test_DataProtocol_receive() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var bytes = arena.allocateFrom("hello"); + var dat = Data.init(bytes, bytes.byteSize(), arena); + var result = MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + assertEquals(6, result); + } + } +} From f69d84a5ae5a4420949d524f60d9852db523a2da Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 11 Jul 2025 20:58:09 +0900 Subject: [PATCH 092/178] Drop the java.toolchain version to 17, which has a Corretto LTS release (#304) --- .github/actions/prepare_env/action.yml | 2 +- SwiftKitCore/build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index c63b87cf..8fc986e0 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -11,7 +11,7 @@ runs: java-version: | 24 21 - 19 + 17 cache: 'gradle' - name: Set JAVA_HOME_{N} shell: bash diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 3d5907f2..78cdbeb2 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -25,11 +25,11 @@ repositories { java { toolchain { - // JDK 19 is the last JDK to support Java 7 - languageVersion.set(JavaLanguageVersion.of(19)) + languageVersion.set(JavaLanguageVersion.of(17)) } // Support Android 6+ (Java 7) sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 } dependencies { From 17210b73ab357d118eb565a98a64ad0fb3df8650 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 11 Jul 2025 21:09:23 +0900 Subject: [PATCH 093/178] WIP: Update readme to reflect latest work Work in progress, but it's time to update the readme --- README.md | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index b7ad4def..f9f4ca3f 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ This repository contains two approaches to Swift/Java interoperability. -- A Swift library (`JavaKit`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. -- The `jextract-swift` tool which is similar to the JDK's `jextract` which allows to extract Java sources which are used - to efficiently call into Swift _from Java_. +- Swift library (`JavaKit`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. +- The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. ## :construction: :construction: :construction: Early Development :construction: :construction: :construction: @@ -16,34 +15,48 @@ The primary purpose of this repository is to create an environment for collabora ## Dependencies -### Required Swift Development Toolchains +### Required JDK versions -To build and use this project, currently, you will need to download a custom toolchain which includes some improvements in Swift that this project relies on: +This project consists of different modules which have different Swift and Java runtime requirements. -**Required toolchain download:** +## JavaKit macros -Currently this project supports Swift `6.0.x` and we are working on supporting later releases. +JavaKit is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. -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. +It is possible to generate Swift bindings to Java libraries using JavaKit by using the `swift-java wrap-java` command. -### Required JDK versions +Required language/runtime versions: +- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration +- **Swift 6.0.x**, because the library uses modern Swift macros -This project consists of different modules which have different Swift and Java runtime requirements. +**swift-java jextract** -**JavaKit** – the Swift macros allowing the invocation of Java libraries from Swift +Is a source generator which will **generate Java bindings to existing Swift libraries**. +Its inputs are Swift sources or packages, and outputs are generated Swift and Java code necessary to call these functions efficiently from Java. -- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration -- **Swift 6.0.x**, because the library uses modern Swift macros +## swift-java jextract --mode=ffm (default) + +This mode provides the most flexibility and performance, and allows to decrease the amount of data being copied between Swift and Java. +This does require the use of the relatively recent [JEP-454: Foreign Function & Memory API](https://openjdk.org/jeps/454), which is only available since JDK22, and will become part of JDK LTS releases with JDK 25 (depending on your JDK vendor). + +This is the primary way we envision calling Swift code from server-side Java libraries and applications. + +Required language/runtime versions: +- **Swift 6.1**, because of dependence on rich swift interface files +- **JDK 24+** + - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-24. + +## swift-java jextract --mode=jni -**jextract-swift** – the source generator that ingests .swiftinterface files and makes them available to be called from generated Java sources +In this mode, the generated sources will use the legacy JNI approach to calling native code. -- **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. +This mode is more limited in some performance and flexibility that it can offer, however it is the most compatible, since even very old JVM's as well as even Android systems can be supported by this mode. +We recommend this mode when FFM is not available, or wide ranging deployment compatibility is your priority. When performance is paramaunt, we recommend the FFM mode instead. -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. +Required language/runtime versions: +- **Swift 6.1**, because of dependence on rich swift interface files +- **Java 7+**, including -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 From ac8040060e71162f6d184789da3e3ca64523d918 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 11 Jul 2025 23:58:34 +0200 Subject: [PATCH 094/178] [JExtract] Generate JNI code for memory management with `SwiftKitCore` (#302) --- .../com/example/swift/HelloJava2SwiftJNI.java | 7 +- .../com/example/swift/MySwiftClassTest.java | 17 +++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 62 +++++++++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 73 ++++++++++++++++--- .../swift/swiftkit/core/JNISwiftInstance.java | 2 +- .../JNI/JNIClassTests.swift | 54 +++++++++++--- 6 files changed, 175 insertions(+), 40 deletions(-) diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 66366a19..38860c06 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -19,6 +19,7 @@ // Import javakit/swiftkit support libraries import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; public class HelloJava2SwiftJNI { @@ -40,8 +41,10 @@ static void examples() { MySwiftClass.method(); - MySwiftClass myClass = MySwiftClass.init(10, 5); - MySwiftClass myClass2 = MySwiftClass.init(); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass myClass = MySwiftClass.init(10, 5, arena); + MySwiftClass myClass2 = MySwiftClass.init(arena); + } System.out.println("DONE."); } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 994240cb..ec0e31dd 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -14,24 +14,25 @@ package com.example.swift; -import com.example.swift.MySwiftLibrary; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import static org.junit.jupiter.api.Assertions.*; public class MySwiftClassTest { @Test void init_noParameters() { - MySwiftClass c = MySwiftClass.init(); - assertNotNull(c); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(arena); + assertNotNull(c); + } } @Test void init_withParameters() { - MySwiftClass c = MySwiftClass.init(1337, 42); - assertNotNull(c); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(1337, 42, arena); + assertNotNull(c); + } } - } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ac775638..6a1c549a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -12,6 +12,19 @@ // //===----------------------------------------------------------------------===// + +// MARK: Defaults + +extension JNISwift2JavaGenerator { + /// Default set Java imports for every generated file + static let defaultJavaImports: Array = [ + "org.swift.swiftkit.core.*", + "org.swift.swiftkit.core.util.*", + ] +} + +// MARK: Printing + extension JNISwift2JavaGenerator { func writeExportedJavaSources() throws { var printer = CodePrinter() @@ -72,6 +85,7 @@ extension JNISwift2JavaGenerator { private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) + printImports(&printer) printNominal(&printer, decl) { printer in printer.print( @@ -87,14 +101,10 @@ extension JNISwift2JavaGenerator { """ ) - printer.println() - printer.print( """ - private long selfPointer; - - private \(decl.swiftNominal.name)(long selfPointer) { - this.selfPointer = selfPointer; + public \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); } """ ) @@ -108,6 +118,8 @@ extension JNISwift2JavaGenerator { for method in decl.methods { printFunctionBinding(&printer, method) } + + printDestroyFunction(&printer, decl) } } @@ -130,10 +142,17 @@ extension JNISwift2JavaGenerator { ) } + private func printImports(_ printer: inout CodePrinter) { + for i in JNISwift2JavaGenerator.defaultJavaImports { + printer.print("import \(i);") + } + printer.print("") + } + private func printNominal( _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { - printer.printBraceBlock("public final class \(decl.swiftNominal.name)") { printer in + printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in body(&printer) } } @@ -160,7 +179,7 @@ extension JNISwift2JavaGenerator { printer.print( """ long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); - return new \(type.qualifiedName)(selfPointer); + return new \(type.qualifiedName)(selfPointer, swiftArena$); """ ) } @@ -182,6 +201,26 @@ extension JNISwift2JavaGenerator { ) } + /// Prints the destroy function for a `JNISwiftInstance` + private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printer.print("private static native void $destroy(long selfPointer);") + + printer.print("@Override") + printer.printBraceBlock("protected Runnable $createDestroyFunction()") { printer in + printer.print( + """ + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + \(type.swiftNominal.name).$destroy($selfPointer); + } + }; + """ + ) + } + } + /// Renders a Java function signature /// /// `func method(x: Int, y: Int) -> Int` becomes @@ -189,7 +228,12 @@ extension JNISwift2JavaGenerator { private func renderFunctionSignature(_ decl: ImportedFunc) -> String { let translatedDecl = translatedDecl(for: decl) let resultType = translatedDecl.translatedFunctionSignature.resultType - let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + + if decl.isInitializer { + parameters.append("SwiftArena swiftArena$") + } + let throwsClause = decl.isThrowing ? " throws Exception" : "" return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index b4d73873..a14c2078 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -13,6 +13,11 @@ //===----------------------------------------------------------------------===// import JavaTypes +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif extension JNISwift2JavaGenerator { func writeSwiftThunkSources() throws { @@ -96,6 +101,8 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, method) printer.println() } + + printDestroyFunctionThunk(&printer, type) } private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { @@ -199,24 +206,18 @@ extension JNISwift2JavaGenerator { resultType: JavaType, _ body: (inout CodePrinter) -> Void ) { - var jniSignature = parameters.reduce(into: "") { signature, parameter in + let jniSignature = parameters.reduce(into: "") { signature, parameter in signature += parameter.type.jniTypeSignature } - // Escape signature characters - jniSignature = jniSignature - .replacingOccurrences(of: "_", with: "_1") - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: ";", with: "_2") - .replacingOccurrences(of: "[", with: "_3") - let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") - + "_\(parentName)_" - + javaMethodName + + "_\(parentName.escapedJNIIdentifier)_" + + javaMethodName.escapedJNIIdentifier + "__" - + jniSignature + + jniSignature.escapedJNIIdentifier + let translatedParameters = parameters.map { "\($0.name): \($0.type.jniTypeName)" } @@ -251,6 +252,30 @@ extension JNISwift2JavaGenerator { ) } + /// Prints the implementation of the destroy function. + private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printCDecl( + &printer, + javaMethodName: "$destroy", + parentName: type.swiftNominal.name, + parameters: [ + JavaParameter(name: "selfPointer", type: .long) + ], + isStatic: true, + resultType: .void + ) { printer in + // Deinitialize the pointer allocated (which will call the VWT destroy method) + // then deallocate the memory. + printer.print( + """ + let pointer = UnsafeMutablePointer<\(type.qualifiedName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + """ + ) + } + } + /// Renders the arguments for making a downcall private func renderDowncallArguments( swiftFunctionSignature: SwiftFunctionSignature, @@ -266,3 +291,29 @@ extension JNISwift2JavaGenerator { .joined(separator: ", ") } } + +extension String { + /// Returns a version of the string correctly escaped for a JNI + var escapedJNIIdentifier: String { + self.map { + if $0 == "_" { + return "_1" + } else if $0 == "/" { + return "_" + } else if $0 == ";" { + return "_2" + } else if $0 == "[" { + return "_3" + } else if $0.isASCII && ($0.isLetter || $0.isNumber) { + return String($0) + } else if let utf16 = $0.utf16.first { + // Escape any non-alphanumeric to their UTF16 hex encoding + let utf16Hex = String(format: "%04x", utf16) + return "_0\(utf16Hex)" + } else { + fatalError("Invalid JNI character: \($0)") + } + } + .joined() + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index 891745a2..f9966793 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -39,7 +39,7 @@ protected JNISwiftInstance(long pointer, SwiftArena arena) { * * @return a function that is called when the value should be destroyed. */ - abstract Runnable $createDestroyFunction(); + protected abstract Runnable $createDestroyFunction(); @Override public SwiftInstanceCleanup createCleanupAction() { diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index c39d5815..0c0f4f02 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -44,8 +44,11 @@ struct JNIClassTests { // Swift module: SwiftModule package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; - public final class MyClass { + public final class MyClass extends JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -55,12 +58,25 @@ struct JNIClassTests { return true; } - private long selfPointer; - - private MyClass(long selfPointer) { - this.selfPointer = selfPointer; + public MyClass(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); } """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + MyClass.$destroy($selfPointer); + } + }; + } + """ ]) } @@ -116,9 +132,9 @@ struct JNIClassTests { * public init(x: Int64, y: Int64) * } */ - public static MyClass init(long x, long y) { + public static MyClass init(long x, long y, SwiftArena swiftArena$) { long selfPointer = MyClass.allocatingInit(x, y); - return new MyClass(selfPointer); + return new MyClass(selfPointer, swiftArena$); } """, """ @@ -128,9 +144,9 @@ struct JNIClassTests { * public init() * } */ - public static MyClass init() { + public static MyClass init(SwiftArena swiftArena$) { long selfPointer = MyClass.allocatingInit(); - return new MyClass(selfPointer); + return new MyClass(selfPointer, swiftArena$); } """, """ @@ -170,4 +186,24 @@ struct JNIClassTests { ] ) } + + @Test + func destroyFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024destroy__J") + func Java_com_example_swift_MyClass__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + } + """ + ] + ) + } } From 7bec68559498cbdfc18abd288988b737f5b5d69e Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 13:08:53 +0200 Subject: [PATCH 095/178] [JExtract] Add support for JNI member methods (#305) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 16 +++ .../com/example/swift/HelloJava2SwiftJNI.java | 6 + .../com/example/swift/MySwiftClassTest.java | 26 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 37 ++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 124 +++++++++++------- .../Exceptions/ExceptionHandling.swift | 2 +- .../JNI/JNIClassTests.swift | 47 +++++++ 7 files changed, 210 insertions(+), 48 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index fca6236f..82958464 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -34,4 +34,20 @@ public class MySwiftClass { deinit { p("deinit called!") } + + public func sum() -> Int64 { + return x + y + } + + public func xMultiplied(by z: Int64) -> Int64 { + return x * z; + } + + enum MySwiftClassError: Error { + case swiftError + } + + public func throwingFunction() throws { + throw MySwiftClassError.swiftError + } } diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 38860c06..575859ec 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -44,6 +44,12 @@ static void examples() { try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); + + try { + myClass.throwingFunction(); + } catch (Exception e) { + System.out.println("Caught exception: " + e.getMessage()); + } } System.out.println("DONE."); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index ec0e31dd..3982b480 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -35,4 +35,30 @@ void init_withParameters() { assertNotNull(c); } } + + @Test + void sum() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(30, c.sum()); + } + } + + @Test + void xMultiplied() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(200, c.xMultiplied(10)); + } + } + + @Test + void throwingFunction() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + Exception exception = assertThrows(Exception.class, () -> c.throwingFunction()); + + assertEquals("swiftError", exception.getMessage()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 6a1c549a..dce399a9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -164,12 +164,49 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + if decl.isStatic || decl.isInitializer || !decl.hasParent { + printStaticFunctionBinding(&printer, decl) + } else { + printMemberMethodBindings(&printer, decl) + } + } + + private func printStaticFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { printDeclDocumentation(&printer, decl) printer.print( "public static native \(renderFunctionSignature(decl));" ) } + /// Renders Java bindings for member methods + /// + /// Member methods are generated as a function that extracts the `selfPointer` + /// and passes it down to another native function along with the arguments + /// to call the Swift implementation. + private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = translatedDecl(for: decl) + + printDeclDocumentation(&printer, decl) + printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in + var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) + arguments.append("selfPointer") + + let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return " + + printer.print( + """ + long selfPointer = this.pointer(); + \(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", "))); + """ + ) + } + + let returnType = translatedDecl.translatedFunctionSignature.resultType + var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + parameters.append("long selfPointer") + printer.print("private static native \(returnType) $\(translatedDecl.name)(\(parameters.joined(separator: ", ")));") + } + private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { let translatedDecl = translatedDecl(for: decl) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index a14c2078..24532ca1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -136,67 +136,97 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ decl: ImportedFunc ) { + // Free functions does not have a parent + if decl.isStatic || !decl.hasParent { + self.printSwiftStaticFunctionThunk(&printer, decl) + } else { + self.printSwiftMemberFunctionThunk(&printer, decl) + } + } + + private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { let translatedDecl = self.translatedDecl(for: decl) - let parentName = translatedDecl.parentName - let swiftReturnType = decl.functionSignature.result.type - printCDecl(&printer, decl) { printer in - let downcallParameters = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature + printCDecl( + &printer, + javaMethodName: translatedDecl.name, + parentName: translatedDecl.parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: true, + resultType: translatedDecl.translatedFunctionSignature.resultType + ) { printer in + // For free functions the parent is the Swift module + let parentName = decl.parentType?.asNominalTypeDeclaration?.qualifiedName ?? swiftModuleName + self.printFunctionDowncall(&printer, decl, calleeName: parentName) + } + } + + private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = self.translatedDecl(for: decl) + let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName + + printCDecl( + &printer, + javaMethodName: "$\(translatedDecl.name)", + parentName: translatedDecl.parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters + [ + JavaParameter(name: "selfPointer", type: .long) + ], + isStatic: true, + resultType: translatedDecl.translatedFunctionSignature.resultType + ) { printer in + printer.print( + """ + let self$ = UnsafeMutablePointer<\(swiftParentName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + """ ) - let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = - "\(tryClause)\(parentName).\(decl.name)(\(downcallParameters))" - - let innerBody = - if swiftReturnType.isVoid { - functionDowncall - } else { - """ - let result = \(functionDowncall) - return result.getJNIValue(in: environment) - """ - } + self.printFunctionDowncall(&printer, decl, calleeName: "self$.pointee") + } + } + + private func printFunctionDowncall( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + calleeName: String + ) { + let translatedDecl = self.translatedDecl(for: decl) + let swiftReturnType = decl.functionSignature.result.type + + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + let tryClause: String = decl.isThrowing ? "try " : "" + let functionDowncall = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + let returnStatement = + 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( + if decl.isThrowing { + let dummyReturn = + !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" + printer.print( """ do { - \(innerBody) + \(returnStatement) } catch { environment.throwAsException(error) \(dummyReturn) } """ - ) - } else { - printer.print(innerBody) - } + ) + } else { + printer.print(returnStatement) } } - private func printCDecl( - _ printer: inout CodePrinter, - _ decl: ImportedFunc, - _ body: (inout CodePrinter) -> Void - ) { - let translatedDecl = translatedDecl(for: decl) - let parentName = translatedDecl.parentName - - printCDecl( - &printer, - javaMethodName: translatedDecl.name, - parentName: parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters, - isStatic: decl.isStatic || decl.isInitializer || !decl.hasParent, - resultType: translatedDecl.translatedFunctionSignature.resultType, - body - ) - } - private func printCDecl( _ printer: inout CodePrinter, javaMethodName: String, diff --git a/Sources/JavaKit/Exceptions/ExceptionHandling.swift b/Sources/JavaKit/Exceptions/ExceptionHandling.swift index 02537281..d5ca1107 100644 --- a/Sources/JavaKit/Exceptions/ExceptionHandling.swift +++ b/Sources/JavaKit/Exceptions/ExceptionHandling.swift @@ -39,7 +39,7 @@ extension JNIEnvironment { } // Otherwise, create a exception with a message. - _ = try! JavaClass.withJNIClass(in: self) { exceptionClass in + _ = try! Exception.withJNIClass(in: self) { exceptionClass in interface.ThrowNew(self, exceptionClass, String(describing: error)) } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 0c0f4f02..41802e55 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -33,6 +33,8 @@ struct JNIClassTests { self.x = 0 self.y = 0 } + + public func doSomething(x: Int64) {} } """ @@ -206,4 +208,49 @@ struct JNIClassTests { ] ) } + + @Test + func memberMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ + public void doSomething(long x) { + long selfPointer = this.pointer(); + MyClass.$doSomething(x, selfPointer); + } + """, + """ + private static native void $doSomething(long x, long selfPointer); + """ + ] + ) + } + + @Test + func memberMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") + func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + } + """, + ] + ) + } } From 94d5f345335062bbe9833e3c84d701c5101c7d8e Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 13 Jul 2025 09:45:50 +0200 Subject: [PATCH 096/178] [JExtract/JNI] Add support for variables (#306) * add support for variables * prefix boolean variables with `is` to match Java conventions * add confined arena constructor with current thread * add variable tests * PR feedback * add test for isWarm --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 25 ++ .../MySwiftLibrary/MySwiftLibrary.swift | 2 + .../Sources/MySwiftLibrary/swift-java.config | 2 +- .../com/example/swift/HelloJava2SwiftJNI.java | 4 +- .../com/example/swift/MySwiftClassTest.java | 67 ++- .../com/example/swift/MySwiftLibraryTest.java | 7 + .../Convenience/String+Extensions.swift | 12 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 4 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 18 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 12 + ...ISwift2JavaGenerator+JavaTranslation.swift | 9 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 43 +- .../SwiftTypes/SwiftFunctionSignature.swift | 11 +- .../core/ConfinedSwiftMemorySession.java | 4 + .../JNI/JNIVariablesTests.swift | 386 ++++++++++++++++++ 15 files changed, 584 insertions(+), 22 deletions(-) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 82958464..386a7227 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -16,6 +16,31 @@ public class MySwiftClass { let x: Int64 let y: Int64 + public let constant: Int64 = 100 + public var mutable: Int64 = 0 + public var product: Int64 { + return x * y + } + public var throwingVariable: Int64 { + get throws { + throw MySwiftClassError.swiftError + } + } + public var mutableDividedByTwo: Int64 { + get { + return mutable / 2 + } + set { + mutable = newValue * 2 + } + } + public let warm: Bool = false + public var getAsync: Int64 { + get async { + return 42 + } + } + public static func method() { p("Hello from static method in a class!") } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index be04bc08..1dd84547 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -23,6 +23,8 @@ import Darwin.C #endif +public var globalVariable: Int64 = 0 + public func helloWorld() { p("\(#function)") } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index 46bf1f1c..bb637f34 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,4 +1,4 @@ { "javaPackage": "com.example.swift", - "mode": "jni" + "mode": "jni", } diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 575859ec..8e7ca284 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -41,10 +41,12 @@ static void examples() { MySwiftClass.method(); - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); + System.out.println("myClass.isWarm: " + myClass.isWarm()); + try { myClass.throwingFunction(); } catch (Exception e) { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 3982b480..bf244416 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -22,7 +22,7 @@ public class MySwiftClassTest { @Test void init_noParameters() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(arena); assertNotNull(c); } @@ -30,7 +30,7 @@ void init_noParameters() { @Test void init_withParameters() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(1337, 42, arena); assertNotNull(c); } @@ -38,7 +38,7 @@ void init_withParameters() { @Test void sum() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(30, c.sum()); } @@ -46,7 +46,7 @@ void sum() { @Test void xMultiplied() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.xMultiplied(10)); } @@ -54,11 +54,68 @@ void xMultiplied() { @Test void throwingFunction() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.throwingFunction()); assertEquals("swiftError", exception.getMessage()); } } + + @Test + void constant() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(100, c.getConstant()); + } + } + + @Test + void mutable() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(0, c.getMutable()); + c.setMutable(42); + assertEquals(42, c.getMutable()); + } + } + + @Test + void product() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(200, c.getProduct()); + } + } + + @Test + void throwingVariable() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + + Exception exception = assertThrows(Exception.class, () -> c.getThrowingVariable()); + + assertEquals("swiftError", exception.getMessage()); + } + } + + @Test + void mutableDividedByTwo() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(0, c.getMutableDividedByTwo()); + c.setMutable(20); + assertEquals(10, c.getMutableDividedByTwo()); + c.setMutableDividedByTwo(5); + assertEquals(10, c.getMutable()); + } + } + + @Test + void isWarm() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertFalse(c.isWarm()); + } + } } \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index f1cefd19..5c9c2358 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -54,4 +54,11 @@ void call_writeString_jextract() { assertEquals(string.length(), reply); } + + @Test + void globalVariable() { + assertEquals(0, MySwiftLibrary.getGlobalVariable()); + MySwiftLibrary.setGlobalVariable(100); + assertEquals(100, MySwiftLibrary.getGlobalVariable()); + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 53781cef..1878ba2f 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -22,4 +22,14 @@ extension String { return "\(f.uppercased())\(String(dropFirst()))" } -} \ No newline at end of file + + /// Returns whether the string is of the format `isX` + var hasJavaBooleanNamingConvention: Bool { + guard self.hasPrefix("is"), self.count > 2 else { + return false + } + + let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) + return self[thirdCharacterIndex].isUppercase + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index db706a91..e548db5c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -127,8 +127,8 @@ extension FFMSwift2JavaGenerator { // Name. let javaName = switch decl.apiKind { - case .getter: "get\(decl.name.toCamelCase)" - case .setter: "set\(decl.name.toCamelCase)" + case .getter: decl.javaGetterName + case .setter: decl.javaSetterName case .function, .initializer: decl.name } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 76d53d88..4b8a478a 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -154,3 +154,21 @@ extension ImportedFunc: Hashable { return lhs === rhs } } + +extension ImportedFunc { + var javaGetterName: String { + let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + if !returnsBoolean { + return "get\(self.name.toCamelCase)" + } else if !self.name.hasJavaBooleanNamingConvention { + return "is\(self.name.toCamelCase)" + } else { + return self.name.toCamelCase + } + } + + var javaSetterName: String { + "set\(self.name.toCamelCase)" + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index dce399a9..ec0fa9c4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -79,6 +79,11 @@ extension JNISwift2JavaGenerator { printFunctionBinding(&printer, decl) printer.println() } + + for decl in analysis.importedGlobalVariables { + printFunctionBinding(&printer, decl) + printer.println() + } } } @@ -113,10 +118,17 @@ extension JNISwift2JavaGenerator { for initializer in decl.initializers { printInitializerBindings(&printer, initializer, type: decl) + printer.println() } for method in decl.methods { printFunctionBinding(&printer, method) + printer.println() + } + + for variable in decl.variables { + printFunctionBinding(&printer, variable) + printer.println() } printDestroyFunction(&printer, decl) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index f006b4a8..179991df 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -37,8 +37,15 @@ extension JNISwift2JavaGenerator { // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName + // Name. + let javaName = switch decl.apiKind { + case .getter: decl.javaGetterName + case .setter: decl.javaSetterName + case .function, .initializer: decl.name + } + return TranslatedFunctionDecl( - name: decl.name, + name: javaName, parentName: parentName, translatedFunctionSignature: translatedFunctionSignature ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 24532ca1..c32e0e27 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -87,6 +87,11 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, decl) printer.println() } + + for decl in analysis.importedGlobalVariables { + printSwiftFunctionThunk(&printer, decl) + printer.println() + } } private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { @@ -102,6 +107,11 @@ extension JNISwift2JavaGenerator { printer.println() } + for variable in type.variables { + printSwiftFunctionThunk(&printer, variable) + printer.println() + } + printDestroyFunctionThunk(&printer, type) } @@ -192,19 +202,34 @@ extension JNISwift2JavaGenerator { let translatedDecl = self.translatedDecl(for: decl) let swiftReturnType = decl.functionSignature.result.type - let downcallParameters = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature - ) let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + let result: String + switch decl.apiKind { + case .function, .initializer: + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + result = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + case .getter: + result = "\(tryClause)\(calleeName).\(decl.name)" + + case .setter: + guard let newValueParameter = decl.functionSignature.parameters.first else { + fatalError("Setter did not contain newValue parameter: \(decl)") + } + + result = "\(calleeName).\(decl.name) = \(renderJNIToSwiftConversion("newValue", type: newValueParameter.type))" + } let returnStatement = if swiftReturnType.isVoid { - functionDowncall + result } else { """ - let result = \(functionDowncall) + let result = \(result) return result.getJNIValue(in: environment) """ } @@ -320,6 +345,10 @@ extension JNISwift2JavaGenerator { } .joined(separator: ", ") } + + private func renderJNIToSwiftConversion(_ variableName: String, type: SwiftType) -> String { + "\(type)(fromJNI: \(variableName), in: environment!)" + } } extension String { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 8f0b3128..5cca79bf 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -211,11 +211,11 @@ extension SwiftFunctionSignature { switch binding.accessorBlock?.accessors { case .getter(let getter): if let getter = getter.as(AccessorDeclSyntax.self) { - effectSpecifiers = Self.effectSpecifiers(from: getter) + effectSpecifiers = try Self.effectSpecifiers(from: getter) } case .accessors(let accessors): if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { - effectSpecifiers = Self.effectSpecifiers(from: getter) + effectSpecifiers = try Self.effectSpecifiers(from: getter) } default: break @@ -232,11 +232,14 @@ extension SwiftFunctionSignature { } } - private static func effectSpecifiers(from decl: AccessorDeclSyntax) -> [SwiftEffectSpecifier] { + private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] { var effectSpecifiers = [SwiftEffectSpecifier]() if decl.effectSpecifiers?.throwsClause != nil { effectSpecifiers.append(.throws) } + if let asyncSpecifier = decl.effectSpecifiers?.asyncSpecifier { + throw SwiftFunctionTranslationError.async(asyncSpecifier) + } return effectSpecifiers } } @@ -254,7 +257,7 @@ extension VariableDeclSyntax { /// - Parameters: /// - binding the pattern binding in this declaration. func supportedAccessorKinds(binding: PatternBindingSyntax) -> SupportedAccessorKinds { - if self.bindingSpecifier == .keyword(.let) { + if self.bindingSpecifier.tokenKind == .keyword(.let) { return [.get] } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index cd8f2dd1..1b6821ca 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -28,6 +28,10 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final ConfinedResourceList resources; + public ConfinedSwiftMemorySession() { + this(Thread.currentThread()); + } + public ConfinedSwiftMemorySession(Thread owner) { this.owner = owner; this.state = new AtomicInteger(ACTIVE); diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift new file mode 100644 index 00000000..f09789d4 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -0,0 +1,386 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIVariablesTests { + let membersSource = + """ + public class MyClass { + public let constant: Int64 + public var mutable: Int64 + public var computed: Int64 { + return 0 + } + public var computedThrowing: Int64 { + get throws { return 0 } + } + public var getterAndSetter: Int64 { + get { return 0 } + set { } + } + public var someBoolean: Bool + public let isBoolean: Bool + } + """ + + @Test + func constant_javaBindings() throws { + try assertOutput(input: membersSource, .jni, .java, expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public let constant: Int64 + * } + */ + public long getConstant() { + long selfPointer = this.pointer(); + return MyClass.$getConstant(selfPointer); + } + """, + """ + private static native long $getConstant(long selfPointer); + """ + ]) + } + + @Test + func constant_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") + func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.constant + return result.getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test + func mutable_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var mutable: Int64 + * } + */ + public long getMutable() { + long selfPointer = this.pointer(); + return MyClass.$getMutable(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var mutable: Int64 + * } + */ + public void setMutable(long newValue) { + long selfPointer = this.pointer(); + MyClass.$setMutable(newValue, selfPointer); + } + """, + """ + private static native long $getMutable(long selfPointer); + """, + """ + private static native void $setMutable(long newValue, long selfPointer); + """ + ] + ) + } + + @Test + func mutable_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") + func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.mutable + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") + func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } + + @Test + func computed_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var computed: Int64 + * } + */ + public long getComputed() { + long selfPointer = this.pointer(); + return MyClass.$getComputed(selfPointer); + } + """, + """ + private static native long $getComputed(long selfPointer); + """, + ] + ) + } + + @Test + func computed_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") + func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.computed + return result.getJNIValue(in: environment) + } + """, + ] + ) + } + + @Test + func computedThrowing_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var computedThrowing: Int64 + * } + */ + public long getComputedThrowing() throws Exception { + long selfPointer = this.pointer(); + return MyClass.$getComputedThrowing(selfPointer); + } + """, + """ + private static native long $getComputedThrowing(long selfPointer); + """, + ] + ) + } + + @Test + func computedThrowing_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") + func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + do { + let result = try self$.pointee.computedThrowing + return result.getJNIValue(in: environment) + } catch { + environment.throwAsException(error) + return Int64.jniPlaceholderValue + } + } + """, + ] + ) + } + + @Test + func getterAndSetter_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var getterAndSetter: Int64 + * } + */ + public long getGetterAndSetter() { + long selfPointer = this.pointer(); + return MyClass.$getGetterAndSetter(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var getterAndSetter: Int64 + * } + */ + public void setGetterAndSetter(long newValue) { + long selfPointer = this.pointer(); + MyClass.$setGetterAndSetter(newValue, selfPointer); + } + """, + """ + private static native long $getGetterAndSetter(long selfPointer); + """, + """ + private static native void $setGetterAndSetter(long newValue, long selfPointer); + """ + ] + ) + } + + @Test + func getterAndSetter_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") + func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.getterAndSetter + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") + func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } + + @Test + func someBoolean_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var someBoolean: Bool + * } + */ + public boolean isSomeBoolean() { + long selfPointer = this.pointer(); + return MyClass.$isSomeBoolean(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var someBoolean: Bool + * } + */ + public void setSomeBoolean(boolean newValue) { + long selfPointer = this.pointer(); + MyClass.$setSomeBoolean(newValue, selfPointer); + } + """, + """ + private static native boolean $isSomeBoolean(long selfPointer); + """, + """ + private static native void $setSomeBoolean(boolean newValue, long selfPointer); + """ + ] + ) + } + + @Test + func boolean_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") + func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.someBoolean + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") + func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } +} From 9c0d7ebe061ae3da75bcbd9e7208bb28f9d92507 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 14 Jul 2025 04:53:31 +0200 Subject: [PATCH 097/178] [JExtract/JNI] Omit unsupported types instead of crashing (#313) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 1 + .../Sources/MySwiftLibrary/MySwiftClass.swift | 1 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 16 ++++++-- ...ISwift2JavaGenerator+JavaTranslation.swift | 38 ++++++++++++------- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 19 ++++++++-- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 386a7227..fd0ce488 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -16,6 +16,7 @@ public class MySwiftClass { let x: Int64 let y: Int64 + public let byte: UInt8 = 0 public let constant: Int64 = 100 public var mutable: Int64 = 0 public var product: Int64 { diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 97f5149e..30661045 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -14,6 +14,7 @@ public class MySwiftClass { + public let byte: UInt8 = 0 public var len: Int public var cap: Int diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ec0fa9c4..80b47760 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -176,6 +176,11 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + guard let _ = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + if decl.isStatic || decl.isInitializer || !decl.hasParent { printStaticFunctionBinding(&printer, decl) } else { @@ -196,7 +201,7 @@ extension JNISwift2JavaGenerator { /// and passes it down to another native function along with the arguments /// to call the Swift implementation. private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl) + let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. printDeclDocumentation(&printer, decl) printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in @@ -220,7 +225,10 @@ extension JNISwift2JavaGenerator { } private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } printDeclDocumentation(&printer, decl) printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in @@ -275,7 +283,9 @@ extension JNISwift2JavaGenerator { /// `func method(x: Int, y: Int) -> Int` becomes /// `long method(long x, long y)` private func renderFunctionSignature(_ decl: ImportedFunc) -> String { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + fatalError("Unable to render function signature for a function that cannot be translated: \(decl)") + } let resultType = translatedDecl.translatedFunctionSignature.resultType var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 179991df..9dc433d4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -17,13 +17,19 @@ import JavaTypes extension JNISwift2JavaGenerator { func translatedDecl( for decl: ImportedFunc - ) -> TranslatedFunctionDecl { + ) -> TranslatedFunctionDecl? { if let cached = translatedDecls[decl] { return cached } - let translation = JavaTranslation(swiftModuleName: self.swiftModuleName) - let translated = translation.translate(decl) + let translated: TranslatedFunctionDecl? + do { + let translation = JavaTranslation(swiftModuleName: swiftModuleName) + translated = try translation.translate(decl) + } catch { + self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") + translated = nil + } translatedDecls[decl] = translated return translated @@ -32,8 +38,8 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { let swiftModuleName: String - func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { - let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { + let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature) // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -51,31 +57,31 @@ extension JNISwift2JavaGenerator { ) } - func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) -> TranslatedFunctionSignature { - let parameters = functionSignature.parameters.enumerated().map { idx, param in + func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) throws -> TranslatedFunctionSignature { + let parameters = try functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" - return translate(swiftParam: param, parameterName: parameterName) + return try translate(swiftParam: param, parameterName: parameterName) } - return TranslatedFunctionSignature( + return try TranslatedFunctionSignature( parameters: parameters, resultType: translate(swiftType: functionSignature.result.type) ) } - func translate(swiftParam: SwiftParameter, parameterName: String) -> JavaParameter { - return JavaParameter( + func translate(swiftParam: SwiftParameter, parameterName: String) throws -> JavaParameter { + return try JavaParameter( name: parameterName, type: translate(swiftType: swiftParam.type) ) } - func translate(swiftType: SwiftType) -> JavaType { + func translate(swiftType: SwiftType) throws -> JavaType { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = translate(knownType: knownType) else { - fatalError("unsupported known type: \(knownType)") + throw JavaTranslationError.unsupportedSwiftType(swiftType) } return javaType } @@ -86,7 +92,7 @@ extension JNISwift2JavaGenerator { return .void case .metatype, .optional, .tuple, .function, .existential, .opaque: - fatalError("unsupported type: \(self)") + throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -127,4 +133,8 @@ extension JNISwift2JavaGenerator { let parameters: [JavaParameter] let resultType: JavaType } + + enum JavaTranslationError: Error { + case unsupportedSwiftType(SwiftType) + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c32e0e27..30db69d1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -116,7 +116,11 @@ extension JNISwift2JavaGenerator { } private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + let typeName = translatedDecl.parentName printCDecl( @@ -146,6 +150,11 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ decl: ImportedFunc ) { + guard let _ = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + // Free functions does not have a parent if decl.isStatic || !decl.hasParent { self.printSwiftStaticFunctionThunk(&printer, decl) @@ -155,7 +164,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl) + let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if we can translate the decl. printCDecl( &printer, @@ -172,7 +181,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl) + let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if can translate the decl. let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName printCDecl( @@ -199,7 +208,9 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc, calleeName: String ) { - let translatedDecl = self.translatedDecl(for: decl) + guard let translatedDecl = self.translatedDecl(for: decl) else { + fatalError("Cannot print function downcall for a function that can't be translated: \(decl)") + } let swiftReturnType = decl.functionSignature.result.type let tryClause: String = decl.isThrowing ? "try " : "" From 5ed029daa1a3567b662a769edf18aa74f8631822 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 14 Jul 2025 02:35:22 -0400 Subject: [PATCH 098/178] [JavaKit] Don't include JVM path for Android (#309) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 403617da..81d53b1d 100644 --- a/Package.swift +++ b/Package.swift @@ -226,7 +226,7 @@ let package = Package( exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) ], linkerSettings: [ .unsafeFlags( From 3b63202effbb2fdc4a1705dc20b1369c2d235791 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 15 Jul 2025 08:49:47 +0200 Subject: [PATCH 099/178] add struct tests (#316) --- .../MySwiftLibrary/MySwiftStruct.swift | 33 +++ .../com/example/swift/HelloJava2SwiftJNI.java | 6 + .../com/example/swift/MySwiftStructTest.java | 50 +++++ .../JNI/JNIStructTests.swift | 188 ++++++++++++++++++ 4 files changed, 277 insertions(+) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIStructTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift new file mode 100644 index 00000000..00e2533c --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public struct MySwiftStruct { + private var cap: Int64 + public var len: Int64 + + public init(cap: Int64, len: Int64) { + self.cap = cap + self.len = len + } + + public func getCapacity() -> Int64 { + self.cap + } + + public mutating func increaseCap(by value: Int64) -> Int64 { + precondition(value > 0) + self.cap += value + return self.cap + } +} diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 8e7ca284..21cad317 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -52,6 +52,12 @@ static void examples() { } catch (Exception e) { System.out.println("Caught exception: " + e.getMessage()); } + + MySwiftStruct myStruct = MySwiftStruct.init(12, 34, arena); + System.out.println("myStruct.cap: " + myStruct.getCapacity()); + System.out.println("myStruct.len: " + myStruct.getLen()); + myStruct.increaseCap(10); + System.out.println("myStruct.cap after increase: " + myStruct.getCapacity()); } System.out.println("DONE."); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java new file mode 100644 index 00000000..9eeaf029 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftStructTest { + @Test + void init() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + assertEquals(1337, s.getCapacity()); + assertEquals(42, s.getLen()); + } + } + + @Test + void getAndSetLen() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + s.setLen(100); + assertEquals(100, s.getLen()); + } + } + + @Test + void increaseCap() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + long newCap = s.increaseCap(10); + assertEquals(1347, newCap); + assertEquals(1347, s.getCapacity()); + } + } +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift new file mode 100644 index 00000000..0a883ed1 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIStructTests { + let source = """ + public struct MyStruct { + let x: Int64 + let y: Int64 + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public func doSomething(x: Int64) {} + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + + public final class MyStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + public MyStruct(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + MyStruct.$destroy($selfPointer); + } + }; + } + """ + ]) + } + + @Test + func initializer_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ + public static MyStruct init(long x, long y, SwiftArena swiftArena$) { + long selfPointer = MyStruct.allocatingInit(x, y); + return new MyStruct(selfPointer, swiftArena$); + } + """, + """ + private static native long allocatingInit(long x, long y); + """, + ] + ) + } + + @Test + func initializer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct_allocatingInit__JJ") + func Java_com_example_swift_MyStruct_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test + func destroyFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024destroy__J") + func Java_com_example_swift_MyStruct__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + } + """ + ] + ) + } + + @Test + func memberMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ + public void doSomething(long x) { + long selfPointer = this.pointer(); + MyStruct.$doSomething(x, selfPointer); + } + """, + """ + private static native void $doSomething(long x, long selfPointer); + """ + ] + ) + } + + @Test + func memberMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") + func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + } + """, + ] + ) + } +} From 78b3e200e2946d70c840a38353c505f92fcfc960 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 15 Jul 2025 08:52:03 +0200 Subject: [PATCH 100/178] conform `Int` to `JavaValue` (#317) --- .../BridgedValues/JavaValue+Integers.swift | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift index 01a7bdba..1af1b549 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift @@ -285,3 +285,122 @@ extension Int64: JavaValue { 0 } } + +#if _pointerBitWidth(_32) +extension Int: JavaValue { + + public typealias JNIType = jint + + public static var jvalueKeyPath: WritableKeyPath { \.i } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Int(value) + } + + public static var javaType: JavaType { .int } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallIntMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetIntField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetIntField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticIntMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticIntField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticIntField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewIntArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetIntArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetIntArrayRegion + } + + public static var jniPlaceholderValue: jint { + 0 + } +} +#elseif _pointerBitWidth(_64) +extension Int: JavaValue { + public typealias JNIType = jlong + + public static var jvalueKeyPath: WritableKeyPath { \.j } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Int(value) + } + + public static var javaType: JavaType { .long } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallLongMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetLongField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetLongField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticLongMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticLongField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticLongField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewLongArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetLongArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetLongArrayRegion + } + + public static var jniPlaceholderValue: jlong { + 0 + } +} +#endif From 19a67851589ea61e46768e90b4ec878c28052eed Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 15 Jul 2025 17:33:07 -0700 Subject: [PATCH 101/178] [JExtract/FFM] Bridge Optional parameters (#320) --- .../MySwiftLibrary/MySwiftLibrary.swift | 17 ++ .../com/example/swift/HelloJava2Swift.java | 4 + .../com/example/swift/OptionalImportTest.java | 35 ++++ .../FFM/CDeclLowering/CRepresentation.swift | 2 +- ...Swift2JavaGenerator+FunctionLowering.swift | 91 +++++++++- .../JExtractSwiftLib/FFM/ConversionStep.swift | 8 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 85 +++++++++- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 2 +- .../SwiftTypes/SwiftKnownModules.swift | 2 + .../SwiftTypes/SwiftKnownTypeDecls.swift | 1 + .../org/swift/swiftkit/ffm/SwiftRuntime.java | 40 ++++- .../FunctionLoweringTests.swift | 21 +++ .../OptionalImportTests.swift | 156 ++++++++++++++++++ 14 files changed, 449 insertions(+), 19 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java create mode 100644 Tests/JExtractSwiftTests/OptionalImportTests.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 1bcca4fd..cdd61c12 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -68,6 +68,23 @@ public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int { return data.count } +public func globalReceiveOptional(o1: Int?, o2: (some DataProtocol)?) -> Int { + switch (o1, o2) { + case (nil, nil): + p(", ") + return 0 + case (let v1?, nil): + p("\(v1), ") + return 1 + case (nil, let v2?): + p(", \(v2)") + return 2 + case (let v1?, let v2?): + p("\(v1), \(v2)") + return 3 + } +} + // ==== 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 821b228e..3b083cc0 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -22,6 +22,9 @@ import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; +import java.util.Optional; +import java.util.OptionalLong; + public class HelloJava2Swift { public static void main(String[] args) { @@ -95,6 +98,7 @@ static void examples() { var bytes = arena.allocateFrom("hello"); var dat = Data.init(bytes, bytes.byteSize(), arena); MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(dat)); } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java new file mode 100644 index 00000000..57e8dba6 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +import java.util.Optional; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.*; + +public class OptionalImportTest { + @Test + void test_Optional_receive() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var data = Data.init(origBytes, origBytes.byteSize(), arena); + assertEquals(0, MySwiftLibrary.globalReceiveOptional(OptionalLong.empty(), Optional.empty())); + assertEquals(3, MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(data))); + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 4e44e74b..c2bb53ab 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -125,7 +125,7 @@ extension SwiftKnownTypeDeclKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol, .optional: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 0c6c707c..bb994164 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -194,7 +194,7 @@ struct CdeclLowering { ) case .unsafeBufferPointer, .unsafeMutableBufferPointer: - guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else { + guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else { throw LoweringError.unhandledType(type) } // Typed pointers are lowered to (raw-pointer, count) pair. @@ -253,6 +253,12 @@ struct CdeclLowering { ] )) + case .optional: + guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else { + throw LoweringError.unhandledType(type) + } + return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName) + case .string: // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *') if knownType == .string { @@ -336,8 +342,79 @@ struct CdeclLowering { } throw LoweringError.unhandledType(type) - case .optional: - throw LoweringError.unhandledType(type) + case .optional(let wrapped): + return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName) + } + } + + /// Lower a Swift Optional to cdecl function type. + /// + /// - Parameters: + /// - fn: the Swift function type to lower. + func lowerOptionalParameter( + _ wrappedType: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> LoweredParameter { + // If there is a 1:1 mapping between this Swift type and a C type, lower it to 'UnsafePointer?' + if let _ = try? CType(cdeclType: wrappedType) { + return LoweredParameter( + cdeclParameters: [ + SwiftParameter(convention: .byValue, parameterName: parameterName, type: .optional(knownTypes.unsafePointer(wrappedType))) + ], + conversion: .pointee(.optionalChain(.placeholder)) + ) + } + + switch wrappedType { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + switch knownType { + case .data: + break + case .unsafeRawPointer, .unsafeMutableRawPointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .unsafePointer, .unsafeMutablePointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .void, .string: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .dataProtocol: + throw LoweringError.unhandledType(.optional(wrappedType)) + default: + // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. + throw LoweringError.unhandledType(.optional(wrappedType)) + } + } + + // Lower arbitrary nominal to `UnsafeRawPointer?` + return LoweredParameter( + cdeclParameters: [ + SwiftParameter(convention: .byValue, parameterName: parameterName, type: .optional(knownTypes.unsafeRawPointer)) + ], + conversion: .pointee(.typedPointer(.optionalChain(.placeholder), swiftType: wrappedType)) + ) + + case .existential(let proto), .opaque(let proto): + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName) + } + throw LoweringError.unhandledType(.optional(wrappedType)) + + case .tuple(let tuple): + if tuple.count == 1 { + return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName) + } + throw LoweringError.unhandledType(.optional(wrappedType)) + + case .function, .metatype, .optional: + throw LoweringError.unhandledType(.optional(wrappedType)) } } @@ -527,13 +604,13 @@ struct CdeclLowering { case .void: return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder) - case .string: - // Returning string is not supported at this point. - throw LoweringError.unhandledType(type) - case .data: break + case .string, .optional: + // Not supported at this point. + throw LoweringError.unhandledType(type) + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index c7fa53e3..656d9dd9 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -62,6 +62,8 @@ enum ConversionStep: Equatable { indirect case member(ConversionStep, member: String) + indirect case optionalChain(ConversionStep) + /// Count the number of times that the placeholder occurs within this /// conversion step. var placeholderCount: Int { @@ -71,7 +73,7 @@ enum ConversionStep: Equatable { .typedPointer(let inner, swiftType: _), .unsafeCastPointer(let inner, swiftType: _), .populatePointer(name: _, assumingType: _, to: let inner), - .member(let inner, member: _): + .member(let inner, member: _), .optionalChain(let inner): inner.placeholderCount case .initialize(_, arguments: let arguments): arguments.reduce(0) { $0 + $1.argument.placeholderCount } @@ -175,6 +177,10 @@ enum ConversionStep: Equatable { } return nil + case .optionalChain(let step): + let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) + return ExprSyntax(OptionalChainingExprSyntax(expression: inner!)) + case .closureLowering(let parameterSteps, let resultStep): var body: [CodeBlockItemSyntax] = [] diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index e548db5c..9f658188 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -238,7 +238,6 @@ extension FFMSwift2JavaGenerator { 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 @@ -349,6 +348,12 @@ extension FFMSwift2JavaGenerator { ]) ) + case .optional: + guard let genericArgs = swiftNominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unhandledType(swiftType) + } + return try translateOptionalParameter(wrappedType: genericArgs[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + case .string: return TranslatedParameter( javaParameters: [ @@ -414,8 +419,81 @@ extension FFMSwift2JavaGenerator { // Otherwise, not supported yet. throw JavaTranslationError.unhandledType(swiftType) - case .optional: - throw JavaTranslationError.unhandledType(swiftType) + case .optional(let wrapped): + return try translateOptionalParameter(wrappedType: wrapped, convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + } + } + + /// Translate an Optional Swift API parameter to the user-facing Java API parameter. + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + convention: SwiftParameterConvention, + parameterName: String, + loweredParam: LoweredParameter, + methodName: String + ) throws -> TranslatedParameter { + // 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) { + var (translatedClass, lowerFunc) = switch cType.javaType { + case .int: ("OptionalInt", "toOptionalSegmentInt") + case .long: ("OptionalLong", "toOptionalSegmentLong") + case .double: ("OptionalDouble", "toOptionalSegmentDouble") + case .boolean: ("Optional", "toOptionalSegmentBoolean") + case .byte: ("Optional", "toOptionalSegmentByte") + case .char: ("Optional", "toOptionalSegmentCharacter") + case .short: ("Optional", "toOptionalSegmentShort") + case .float: ("Optional", "toOptionalSegmentFloat") + default: + throw JavaTranslationError.unhandledType(.optional(swiftType)) + } + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: JavaType(className: translatedClass)) + ], + conversion: .call(.placeholder, function: "SwiftRuntime.\(lowerFunc)", withArena: true) + ) + } + + switch swiftType { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + switch knownType { + case .data, .dataProtocol: + break + default: + throw JavaTranslationError.unhandledType(.optional(swiftType)) + } + } + + let translatedTy = try self.translate(swiftType: swiftType) + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: JavaType(className: "Optional<\(translatedTy.description)>")) + ], + conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false) + ) + case .existential(let proto), .opaque(let proto): + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try translateOptionalParameter( + wrappedType: concreteTy, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName + ) + } + throw JavaTranslationError.unhandledType(.optional(swiftType)) + case .tuple(let tuple): + if tuple.count == 1 { + return try translateOptionalParameter(wrappedType: tuple[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + } + throw JavaTranslationError.unhandledType(.optional(swiftType)) + default: + throw JavaTranslationError.unhandledType(.optional(swiftType)) } } @@ -585,7 +663,6 @@ extension FFMSwift2JavaGenerator.TranslatedFunctionSignature { } } - extension CType { /// Map lowered C type to Java type for FFM binding. var javaType: JavaType { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 6cf80d9b..a24dabb1 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -101,9 +101,7 @@ extension FFMSwift2JavaGenerator { // 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.util.*", "java.nio.charset.StandardCharsets", ] } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 9dc433d4..2e3ffff8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -112,7 +112,7 @@ extension JNISwift2JavaGenerator { .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer, .data, .dataProtocol: + .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: nil } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 47abb7f0..938d0c4a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -76,6 +76,8 @@ private let swiftSourceFile: SourceFileSyntax = """ public struct UnsafeBufferPointer {} public struct UnsafeMutableBufferPointer {} + public struct Optional {} + // FIXME: Support 'typealias Void = ()' public struct Void {} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index d6af5920..11ff25c4 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -36,6 +36,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case unsafeMutablePointer = "Swift.UnsafeMutablePointer" case unsafeBufferPointer = "Swift.UnsafeBufferPointer" case unsafeMutableBufferPointer = "Swift.UnsafeMutableBufferPointer" + case optional = "Swift.Optional" case void = "Swift.Void" case string = "Swift.String" diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index f734bbef..dc18b445 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -14,14 +14,14 @@ package org.swift.swiftkit.ffm; +import org.swift.swiftkit.core.SwiftInstance; import org.swift.swiftkit.core.util.PlatformUtils; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.Arrays; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import static org.swift.swiftkit.core.util.StringUtils.stripPrefix; @@ -425,6 +425,42 @@ public static MemorySegment toCString(String str, Arena arena) { return arena.allocateFrom(str); } + public static MemorySegment toOptionalSegmentInt(OptionalInt opt, Arena arena) { + return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_INT, opt.getAsInt()) : MemorySegment.NULL; + } + + public static MemorySegment toOptionalSegmentLong(OptionalLong opt, Arena arena) { + return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_LONG, opt.getAsLong()) : MemorySegment.NULL; + } + + public static MemorySegment toOptionalSegmentDouble(OptionalDouble opt, Arena arena) { + return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_DOUBLE, opt.getAsDouble()) : MemorySegment.NULL; + } + + public static MemorySegment toOptionalSegmentBoolean(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_BYTE, (byte) (val ? 1 : 0))).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentByte(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_BYTE, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentCharacter(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_CHAR, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentShort(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_SHORT, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentFloat(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_FLOAT, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentInstance(Optional opt) { + return opt.map(instance -> instance.$memorySegment()).orElse(MemorySegment.NULL); + } + private static class swift_getTypeName { /** diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 9422bbdb..8419ec61 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -383,6 +383,27 @@ final class FunctionLoweringTests { ) } + @Test("Lowering optional primitives") + func lowerOptionalParameters() throws { + try assertLoweredFunction( + """ + func fn(a1: Optional, a2: Int?, a3: Point?, a4: (some DataProtocol)?) + """, + sourceFile: """ + import Foundation + struct Point {} + """, + expectedCDecl:""" + @_cdecl("c_fn") + public func c_fn(_ a1: UnsafePointer?, _ a2: UnsafePointer?, _ a3: UnsafeRawPointer?, _ a4: UnsafeRawPointer?) { + fn(a1: a1?.pointee, a2: a2?.pointee, a3: a3?.assumingMemoryBound(to: Point.self).pointee, a4: a4?.assumingMemoryBound(to: Data.self).pointee) + } + """, + expectedCFunction: """ + void c_fn(const ptrdiff_t *a1, const ptrdiff_t *a2, const void *a3, const void *a4) + """) + } + @Test("Lowering read accessor") func lowerGlobalReadAccessor() throws { try assertLoweredVariableAccessor( diff --git a/Tests/JExtractSwiftTests/OptionalImportTests.swift b/Tests/JExtractSwiftTests/OptionalImportTests.swift new file mode 100644 index 00000000..756bb3d9 --- /dev/null +++ b/Tests/JExtractSwiftTests/OptionalImportTests.swift @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import Testing + +final class OptionalImportTests { + let interfaceFile = + """ + import Foundation + + public func receiveOptionalIntSugar(_ arg: Int?) + public func receiveOptionalIntExplicit(_ arg: Optional) + public func receiveOptionalDataProto(_ arg: (some DataProtocol)?)) + """ + + + @Test("Import Optionals: JavaBindings") + func data_javaBindings() throws { + + try assertOutput( + input: interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveOptionalIntSugar__(const ptrdiff_t *arg) + * } + */ + private static class swiftjava_SwiftModule_receiveOptionalIntSugar__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveOptionalIntSugar__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment arg) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(arg); + } + HANDLE.invokeExact(arg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveOptionalIntSugar(_ arg: Int?) + * } + */ + public static void receiveOptionalIntSugar(OptionalLong arg) { + try(var arena$ = Arena.ofConfined()) { + swiftjava_SwiftModule_receiveOptionalIntSugar__.call(SwiftRuntime.toOptionalSegmentLong(arg, arena$)); + } + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveOptionalIntExplicit__(const ptrdiff_t *arg) + * } + */ + private static class swiftjava_SwiftModule_receiveOptionalIntExplicit__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveOptionalIntExplicit__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment arg) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(arg); + } + HANDLE.invokeExact(arg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveOptionalIntExplicit(_ arg: Optional) + * } + */ + public static void receiveOptionalIntExplicit(OptionalLong arg) { + try(var arena$ = Arena.ofConfined()) { + swiftjava_SwiftModule_receiveOptionalIntExplicit__.call(SwiftRuntime.toOptionalSegmentLong(arg, arena$)); + } + } + """, + + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveOptionalDataProto__(const void *arg) + * } + */ + private static class swiftjava_SwiftModule_receiveOptionalDataProto__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveOptionalDataProto__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment arg) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(arg); + } + HANDLE.invokeExact(arg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveOptionalDataProto(_ arg: (some DataProtocol)?) + * } + */ + public static void receiveOptionalDataProto(Optional arg) { + swiftjava_SwiftModule_receiveOptionalDataProto__.call(SwiftRuntime.toOptionalSegmentInstance(arg)); + } + """, + ] + ) + } +} From c0f166d9e1effafe850a098d56ebdd288e82e69e Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 17 Jul 2025 13:53:45 +0900 Subject: [PATCH 102/178] [SwiftKit] Move 'selfPointer' to 'JNISwiftInstance' & downcall trace logs (#322) Co-authored-by: Rintaro Ishizaki --- .../com/example/swift/HelloJava2Swift.java | 25 ++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 4 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 1 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 25 +++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 51 ++++++-- .../org/swift/swiftkit/core/CallTraces.java | 61 +++++++++ .../core/ConfinedSwiftMemorySession.java | 2 +- .../swift/swiftkit/core/JNISwiftInstance.java | 21 ++- .../swift/swiftkit/core/SwiftInstance.java | 35 ++--- .../swift/swiftkit/core/SwiftLibraries.java | 3 +- .../org/swift/swiftkit/core/SwiftObjects.java | 26 ++++ .../ffm/AllocatingAutoSwiftMemorySession.java | 2 +- .../swift/swiftkit/ffm/FFMSwiftInstance.java | 30 +++-- .../swiftkit/ffm/FFMSwiftInstanceCleanup.java | 16 +-- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 40 +----- .../Asserts/TextAssertions.swift | 20 +-- .../JExtractSwiftTests/DataImportTests.swift | 26 ++-- .../FuncCallbackImportTests.swift | 12 +- .../FunctionDescriptorImportTests.swift | 20 +-- .../JNI/JNIClassTests.swift | 69 ++++++---- .../JNI/JNIStructTests.swift | 49 +++++-- .../JNI/JNIVariablesTests.swift | 121 ++++++++++++++---- .../MethodImportTests.swift | 3 - .../OptionalImportTests.swift | 12 +- .../StringPassingTests.swift | 4 +- .../VariableImportTests.swift | 8 +- 26 files changed, 451 insertions(+), 235 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java 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 3b083cc0..cecb1231 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -18,6 +18,7 @@ // Import javakit/swiftkit support libraries +import org.swift.swiftkit.core.CallTraces; import org.swift.swiftkit.core.SwiftLibraries; import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; @@ -43,30 +44,30 @@ static void examples() { long cnt = MySwiftLibrary.globalWriteString("String from Java"); - SwiftRuntime.trace("count = " + cnt); + CallTraces.trace("count = " + cnt); MySwiftLibrary.globalCallMeRunnable(() -> { - SwiftRuntime.trace("running runnable"); + CallTraces.trace("running runnable"); }); - SwiftRuntime.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); + CallTraces.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); MySwiftLibrary.withBuffer((buf) -> { - SwiftRuntime.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); }); // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); // just checking retains/releases work - SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); SwiftRuntime.retain(obj); - SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); SwiftRuntime.release(obj); - SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); obj.setCounter(12); - SwiftRuntime.trace("obj.counter = " + obj.getCounter()); + CallTraces.trace("obj.counter = " + obj.getCounter()); obj.voidMethod(); obj.takeIntMethod(42); @@ -75,9 +76,9 @@ static void examples() { otherObj.voidMethod(); MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena); - SwiftRuntime.trace("swiftValue.capacity = " + swiftValue.getCapacity()); + CallTraces.trace("swiftValue.capacity = " + swiftValue.getCapacity()); swiftValue.withCapLen((cap, len) -> { - SwiftRuntime.trace("withCapLenCallback: cap=" + cap + ", len=" + len); + CallTraces.trace("withCapLenCallback: cap=" + cap + ", len=" + len); }); } @@ -85,12 +86,12 @@ static void examples() { try (var arena = AllocatingSwiftArena.ofConfined()) { var origBytes = arena.allocateFrom("foobar"); var origDat = Data.init(origBytes, origBytes.byteSize(), arena); - SwiftRuntime.trace("origDat.count = " + origDat.getCount()); + CallTraces.trace("origDat.count = " + origDat.getCount()); var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); retDat.withUnsafeBytes((retBytes) -> { var str = retBytes.getString(0); - SwiftRuntime.trace("retStr=" + str); + CallTraces.trace("retStr=" + str); }); } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 13230a32..28b3aba1 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -116,8 +116,8 @@ extension FFMSwift2JavaGenerator { """ public static \(returnTy) call(\(paramsStr)) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(\(argsStr)); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(\(argsStr)); } \(maybeReturn)HANDLE.invokeExact(\(argsStr)); } catch (Throwable ex$) { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index d717255d..10dab4b9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -131,6 +131,7 @@ extension FFMSwift2JavaGenerator { } printer.print("import \(module)") } + printer.println() } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 80b47760..4f4ad4d8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -206,13 +206,15 @@ extension JNISwift2JavaGenerator { printDeclDocumentation(&printer, decl) printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) - arguments.append("selfPointer") + + let selfVarName = "self$" + arguments.append(selfVarName) let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return " printer.print( """ - long selfPointer = this.pointer(); + long \(selfVarName) = this.$memoryAddress(); \(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", "))); """ ) @@ -235,8 +237,8 @@ extension JNISwift2JavaGenerator { let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) printer.print( """ - long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); - return new \(type.qualifiedName)(selfPointer, swiftArena$); + long self$ = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); + return new \(type.qualifiedName)(self$, swiftArena$); """ ) } @@ -262,15 +264,24 @@ extension JNISwift2JavaGenerator { private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { printer.print("private static native void $destroy(long selfPointer);") + let funcName = "$createDestroyFunction" printer.print("@Override") - printer.printBraceBlock("protected Runnable $createDestroyFunction()") { printer in + printer.printBraceBlock("protected Runnable \(funcName)()") { printer in printer.print( """ - long $selfPointer = this.pointer(); + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", + "this", this, + "self", self$); + } return new Runnable() { @Override public void run() { - \(type.swiftNominal.name).$destroy($selfPointer); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$); + } + \(type.swiftNominal.name).$destroy(self$); } }; """ diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 30db69d1..38d4ff79 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -138,9 +138,9 @@ extension JNISwift2JavaGenerator { // TODO: Throwing initializers printer.print( """ - let selfPointer = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) - selfPointer.initialize(to: \(typeName)(\(downcallArguments))) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) + self$.initialize(to: \(typeName)(\(downcallArguments))) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) """ ) } @@ -184,22 +184,20 @@ extension JNISwift2JavaGenerator { let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if can translate the decl. let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( &printer, javaMethodName: "$\(translatedDecl.name)", parentName: translatedDecl.parentName, parameters: translatedDecl.translatedFunctionSignature.parameters + [ - JavaParameter(name: "selfPointer", type: .long) + selfPointerParam ], isStatic: true, resultType: translatedDecl.translatedFunctionSignature.resultType ) { printer in - printer.print( - """ - let self$ = UnsafeMutablePointer<\(swiftParentName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - """ - ) - self.printFunctionDowncall(&printer, decl, calleeName: "self$.pointee") + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, + swiftParentName: swiftParentName, selfPointerParam) + self.printFunctionDowncall(&printer, decl, calleeName: "\(selfVar).pointee") } } @@ -320,28 +318,53 @@ extension JNISwift2JavaGenerator { /// Prints the implementation of the destroy function. private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( &printer, javaMethodName: "$destroy", parentName: type.swiftNominal.name, parameters: [ - JavaParameter(name: "selfPointer", type: .long) + selfPointerParam ], isStatic: true, resultType: .void ) { printer in + let parentName = type.qualifiedName + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) // Deinitialize the pointer allocated (which will call the VWT destroy method) // then deallocate the memory. printer.print( """ - let pointer = UnsafeMutablePointer<\(type.qualifiedName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - pointer.deinitialize(count: 1) - pointer.deallocate() + \(selfVar).deinitialize(count: 1) + \(selfVar).deallocate() """ ) } } + /// Print the necessary conversion logic to go from a `jlong` to a `UnsafeMutablePointer` + /// + /// - Returns: name of the created "self" variable + private func printSelfJLongToUnsafeMutablePointer( + _ printer: inout CodePrinter, + swiftParentName: String, _ selfPointerParam: JavaParameter) -> String { + let newSelfParamName = "self$" + printer.print( + """ + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(\(selfPointerParam.name) != 0, "\(selfPointerParam.name) memory address was null") + let selfBits$ = Int(Int64(fromJNI: \(selfPointerParam.name), in: env$)) + guard let \(newSelfParamName) = UnsafeMutablePointer<\(swiftParentName)>(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + """ + ) + return newSelfParamName + } + + /// Renders the arguments for making a downcall private func renderDowncallArguments( swiftFunctionSignature: SwiftFunctionSignature, diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java new file mode 100644 index 00000000..358205ff --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +public class CallTraces { + public static final boolean TRACE_DOWNCALLS = + Boolean.getBoolean("jextract.trace.downcalls"); + + // Used to manually debug with complete backtraces on every traceDowncall + public static final boolean TRACE_DOWNCALLS_FULL = false; + + public static void traceDowncall(Object... args) { + RuntimeException ex = new RuntimeException(); + + String traceArgs = joinArgs(args); + System.err.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); + if (TRACE_DOWNCALLS_FULL) { + ex.printStackTrace(); + } + } + + public static void trace(Object... args) { + RuntimeException ex = new RuntimeException(); + + String traceArgs = joinArgs(args); + System.err.printf("[java][%s:%d] %s: %s\n", + ex.getStackTrace()[1].getFileName(), + ex.getStackTrace()[1].getLineNumber(), + ex.getStackTrace()[1].getMethodName(), + traceArgs); + } + + private static String joinArgs(Object[] args) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(args[i].toString()); + } + return sb.toString(); + } + +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 1b6821ca..7c6e80fb 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -60,7 +60,7 @@ public void close() { public void register(SwiftInstance instance) { checkValid(); - SwiftInstanceCleanup cleanup = instance.createCleanupAction(); + SwiftInstanceCleanup cleanup = instance.$createCleanup(); this.resources.add(cleanup); } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index f9966793..6b30ed2f 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -14,17 +14,30 @@ package org.swift.swiftkit.core; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; public abstract class JNISwiftInstance extends SwiftInstance { + // Pointer to the "self". + protected final long selfPointer; + /** * The designated constructor of any imported Swift types. * - * @param pointer a pointer to the memory containing the value + * @param selfPointer a pointer to the memory containing the value * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. */ - protected JNISwiftInstance(long pointer, SwiftArena arena) { - super(pointer, arena); + protected JNISwiftInstance(long selfPointer, SwiftArena arena) { + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + arena.register(this); + } + + @Override + public long $memoryAddress() { + return this.selfPointer; } /** @@ -42,7 +55,7 @@ protected JNISwiftInstance(long pointer, SwiftArena arena) { protected abstract Runnable $createDestroyFunction(); @Override - public SwiftInstanceCleanup createCleanupAction() { + public SwiftInstanceCleanup $createCleanup() { final AtomicBoolean statusDestroyedFlag = $statusDestroyedFlag(); Runnable markAsDestroyed = new Runnable() { @Override diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java index 638cb8be..44955cc6 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java @@ -17,49 +17,38 @@ import java.util.concurrent.atomic.AtomicBoolean; public abstract class SwiftInstance { - /// Pointer to the "self". - private final long selfPointer; + + // 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); /** - * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. + * Pointer to the {@code self} of the underlying Swift object or value. + * + * @apiNote When using this pointer one must ensure that the underlying object + * is kept alive using some means (e.g. a class remains retained), as + * this function does not ensure safety of the address in any way. */ - public final long pointer() { - return this.selfPointer; - } + public abstract long $memoryAddress(); /** * Called when the arena has decided the value should be destroyed. *

* Warning: The cleanup action must not capture {@code this}. */ - public abstract SwiftInstanceCleanup createCleanupAction(); - - // 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); + public abstract SwiftInstanceCleanup $createCleanup(); /** * 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}. + * if using an GC managed instance (e.g. using an {@code AutoSwiftMemorySession}. */ public final AtomicBoolean $statusDestroyedFlag() { return this.$state$destroyed; } - /** - * The designated constructor of any imported Swift types. - * - * @param pointer a pointer to the memory containing the value - * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. - */ - protected SwiftInstance(long pointer, SwiftArena arena) { - this.selfPointer = pointer; - arena.register(this); - } - /** * Ensures that this instance has not been destroyed. *

diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index 2abdaff5..3230e52a 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -26,7 +26,6 @@ public final class SwiftLibraries { public static final String STDLIB_DYLIB_NAME = "swiftCore"; public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; - public static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -57,7 +56,7 @@ public static void loadLibrary(String libname) { public static void loadResourceLibrary(String libname) { String resourceName = PlatformUtils.dynamicLibraryName(libname); - if (SwiftLibraries.TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { System.out.println("[swift-java] Loading resource library: " + resourceName); } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java new file mode 100644 index 00000000..c508e90e --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +/** + * Utility functions, similar to @{link java.util.Objects} + */ +public class SwiftObjects { + public static void requireNonZero(long number, String name) { + if (number == 0) { + throw new IllegalArgumentException(String.format("'%s' must not be zero!", name)); + } + } +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java index fecd5202..7063fefb 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java @@ -56,7 +56,7 @@ public void register(SwiftInstance instance) { // We make sure we don't capture `instance` in the // cleanup action, so we can ignore the warning below. - var cleanupAction = instance.createCleanupAction(); + var cleanupAction = instance.$createCleanup(); cleaner.register(instance, cleanupAction); } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java index f4a01aa4..1236bad2 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java @@ -22,6 +22,19 @@ public abstract class FFMSwiftInstance extends SwiftInstance { private final MemorySegment memorySegment; + /** + * The designated constructor of any imported Swift types. + * + * @param segment the memory segment. + * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ + protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { + this.memorySegment = segment; + + // Only register once we have fully initialized the object since this will need the object pointer. + arena.register(this); + } + /** * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. */ @@ -29,24 +42,19 @@ public abstract class FFMSwiftInstance extends SwiftInstance { return this.memorySegment; } + @Override + public long $memoryAddress() { + return $memorySegment().address(); + } + /** * The Swift type metadata of this type. */ public abstract SwiftAnyType $swiftType(); - /** - * The designated constructor of any imported Swift types. - * - * @param segment the memory segment. - * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. - */ - protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { - super(segment.address(), arena); - this.memorySegment = segment; - } @Override - public SwiftInstanceCleanup createCleanupAction() { + public SwiftInstanceCleanup $createCleanup() { var statusDestroyedFlag = $statusDestroyedFlag(); Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java index a5d0829a..c3a9beb6 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java @@ -19,13 +19,13 @@ import java.lang.foreign.MemorySegment; public class FFMSwiftInstanceCleanup implements SwiftInstanceCleanup { - private final MemorySegment selfPointer; - private final SwiftAnyType selfType; + private final MemorySegment memoryAddress; + private final SwiftAnyType type; private final Runnable markAsDestroyed; - public FFMSwiftInstanceCleanup(MemorySegment selfPointer, SwiftAnyType selfType, Runnable markAsDestroyed) { - this.selfPointer = selfPointer; - this.selfType = selfType; + public FFMSwiftInstanceCleanup(MemorySegment memoryAddress, SwiftAnyType type, Runnable markAsDestroyed) { + this.memoryAddress = memoryAddress; + this.type = type; this.markAsDestroyed = markAsDestroyed; } @@ -34,9 +34,9 @@ public void run() { markAsDestroyed.run(); // 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); + if (type != null && memoryAddress != null) { + System.out.println("[debug] Destroy swift value [" + type.getSwiftName() + "]: " + memoryAddress); + SwiftValueWitnessTable.destroy(type, memoryAddress); } } } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index dc18b445..eca6be82 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -15,6 +15,7 @@ package org.swift.swiftkit.ffm; import org.swift.swiftkit.core.SwiftInstance; +import org.swift.swiftkit.core.CallTraces; import org.swift.swiftkit.core.util.PlatformUtils; import java.lang.foreign.*; @@ -24,6 +25,7 @@ import java.util.*; import java.util.stream.Collectors; +import static org.swift.swiftkit.core.CallTraces.traceDowncall; import static org.swift.swiftkit.core.util.StringUtils.stripPrefix; import static org.swift.swiftkit.core.util.StringUtils.stripSuffix; @@ -31,7 +33,6 @@ public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; - public static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -65,33 +66,6 @@ private static SymbolLookup getSymbolLookup() { public SwiftRuntime() { } - public static void traceDowncall(Object... args) { - var ex = new RuntimeException(); - - String traceArgs = Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")); - 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); - } - - public static void trace(Object... args) { - var ex = new RuntimeException(); - - String traceArgs = Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")); - System.out.printf("[java][%s:%d] %s: %s\n", - ex.getStackTrace()[1].getFileName(), - ex.getStackTrace()[1].getLineNumber(), - ex.getStackTrace()[1].getMethodName(), - traceArgs); - } - static MemorySegment findOrThrow(String symbol) { return SYMBOL_LOOKUP.find(symbol) .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: %s".formatted(symbol))); @@ -152,7 +126,7 @@ private static class swift_retainCount { public static long retainCount(MemorySegment object) { var mh$ = swift_retainCount.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_retainCount", object); } return (long) mh$.invokeExact(object); @@ -182,7 +156,7 @@ private static class swift_retain { public static void retain(MemorySegment object) { var mh$ = swift_retain.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_retain", object); } mh$.invokeExact(object); @@ -212,7 +186,7 @@ private static class swift_release { public static void release(MemorySegment object) { var mh$ = swift_release.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_release", object); } mh$.invokeExact(object); @@ -248,7 +222,7 @@ private static class swift_getTypeByName { public static MemorySegment getTypeByName(String string) { var mh$ = swift_getTypeByName.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("_typeByName"); } // TODO: A bit annoying to generate, we need an arena for the conversion... @@ -303,7 +277,7 @@ public static Optional getTypeByMangledNameInEnvironment(String ma // contain this, but we don't need it for type lookup mangledName = stripSuffix(mangledName, "Ma"); mangledName = stripSuffix(mangledName, "CN"); - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_getTypeByMangledNameInEnvironment", mangledName); } try (Arena arena = Arena.ofConfined()) { diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 3c4ad56a..5b70f68a 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -28,7 +28,7 @@ func assertOutput( _ mode: JExtractGenerationMode, _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", - detectChunkByInitialLines: Int = 4, + detectChunkByInitialLines _detectChunkByInitialLines: Int = 4, expectedChunks: [String], fileID: String = #fileID, filePath: String = #filePath, @@ -79,19 +79,22 @@ func assertOutput( 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") + for expectedChunk in expectedChunks { + let expectedLines = expectedChunk.split(separator: "\n") + let detectChunkByInitialLines = min(expectedLines.count, _detectChunkByInitialLines) + precondition(detectChunkByInitialLines > 0, "Chunk size to detect cannot be zero lines!") var matchingOutputOffset: Int? = nil let expectedInitialMatchingLines = expectedLines[0.. (offset+detectChunkByInitialLines) { - let textLinesAtOffset = gotLines[offset.. (lineOffset+detectChunkByInitialLines) { + let textLinesAtOffset = gotLines[lineOffset..!, thisClass: jclass) -> jlong { - let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) - selfPointer.initialize(to: MyClass()) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer.allocate(capacity: 1) + self$.initialize(to: MyClass()) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass_allocatingInit__JJ") func Java_com_example_swift_MyClass_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) - selfPointer.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer.allocate(capacity: 1) + self$.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) } """ ] @@ -200,9 +207,16 @@ struct JNIClassTests { """ @_cdecl("Java_com_example_swift_MyClass__00024destroy__J") func Java_com_example_swift_MyClass__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { - let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - pointer.deinitialize(count: 1) - pointer.deallocate() + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.deinitialize(count: 1) + self$.deallocate() } """ ] @@ -218,14 +232,14 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func doSomething(x: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ public void doSomething(long x) { - long selfPointer = this.pointer(); - MyClass.$doSomething(x, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$doSomething(x, self$); } """, """ @@ -246,7 +260,14 @@ struct JNIClassTests { """ @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 0a883ed1..47b1c4dc 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -63,11 +63,19 @@ struct JNIStructTests { """ @Override protected Runnable $createDestroyFunction() { - long $selfPointer = this.pointer(); + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$createDestroyFunction", + "this", this, + "self", self$); + } return new Runnable() { @Override public void run() { - MyStruct.$destroy($selfPointer); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); + } + MyStruct.$destroy(self$); } }; } @@ -90,8 +98,8 @@ struct JNIStructTests { * } */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { - long selfPointer = MyStruct.allocatingInit(x, y); - return new MyStruct(selfPointer, swiftArena$); + long self$ = MyStruct.allocatingInit(x, y); + return new MyStruct(self$, swiftArena$); } """, """ @@ -112,9 +120,9 @@ struct JNIStructTests { """ @_cdecl("Java_com_example_swift_MyStruct_allocatingInit__JJ") func Java_com_example_swift_MyStruct_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) - selfPointer.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer.allocate(capacity: 1) + self$.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) } """ ] @@ -127,14 +135,20 @@ struct JNIStructTests { input: source, .jni, .swift, - detectChunkByInitialLines: 1, expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyStruct__00024destroy__J") func Java_com_example_swift_MyStruct__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { - let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - pointer.deinitialize(count: 1) - pointer.deallocate() + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.deinitialize(count: 1) + self$.deallocate() } """ ] @@ -156,8 +170,8 @@ struct JNIStructTests { * } */ public void doSomething(long x) { - long selfPointer = this.pointer(); - MyStruct.$doSomething(x, selfPointer); + long self$ = this.$memoryAddress(); + MyStruct.$doSomething(x, self$); } """, """ @@ -178,7 +192,14 @@ struct JNIStructTests { """ @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index f09789d4..3f65bd97 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -48,8 +48,8 @@ struct JNIVariablesTests { * } */ public long getConstant() { - long selfPointer = this.pointer(); - return MyClass.$getConstant(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getConstant(self$); } """, """ @@ -69,7 +69,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } let result = self$.pointee.constant return result.getJNIValue(in: environment) } @@ -94,8 +101,8 @@ struct JNIVariablesTests { * } */ public long getMutable() { - long selfPointer = this.pointer(); - return MyClass.$getMutable(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getMutable(self$); } """, """ @@ -106,8 +113,8 @@ struct JNIVariablesTests { * } */ public void setMutable(long newValue) { - long selfPointer = this.pointer(); - MyClass.$setMutable(newValue, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$setMutable(newValue, self$); } """, """ @@ -131,7 +138,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } let result = self$.pointee.mutable return result.getJNIValue(in: environment) } @@ -139,7 +153,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) } """ @@ -163,8 +184,8 @@ struct JNIVariablesTests { * } */ public long getComputed() { - long selfPointer = this.pointer(); - return MyClass.$getComputed(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getComputed(self$); } """, """ @@ -185,7 +206,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result = self$.pointee.computed return result.getJNIValue(in: environment) } @@ -210,8 +239,8 @@ struct JNIVariablesTests { * } */ public long getComputedThrowing() throws Exception { - long selfPointer = this.pointer(); - return MyClass.$getComputedThrowing(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getComputedThrowing(self$); } """, """ @@ -232,7 +261,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + do { let result = try self$.pointee.computedThrowing return result.getJNIValue(in: environment) @@ -262,8 +299,8 @@ struct JNIVariablesTests { * } */ public long getGetterAndSetter() { - long selfPointer = this.pointer(); - return MyClass.$getGetterAndSetter(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getGetterAndSetter(self$); } """, """ @@ -274,8 +311,8 @@ struct JNIVariablesTests { * } */ public void setGetterAndSetter(long newValue) { - long selfPointer = this.pointer(); - MyClass.$setGetterAndSetter(newValue, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$setGetterAndSetter(newValue, self$); } """, """ @@ -299,7 +336,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result = self$.pointee.getterAndSetter return result.getJNIValue(in: environment) } @@ -307,7 +352,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) } """ @@ -331,8 +384,8 @@ struct JNIVariablesTests { * } */ public boolean isSomeBoolean() { - long selfPointer = this.pointer(); - return MyClass.$isSomeBoolean(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$isSomeBoolean(self$); } """, """ @@ -343,8 +396,8 @@ struct JNIVariablesTests { * } */ public void setSomeBoolean(boolean newValue) { - long selfPointer = this.pointer(); - MyClass.$setSomeBoolean(newValue, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$setSomeBoolean(newValue, self$); } """, """ @@ -368,7 +421,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } let result = self$.pointee.someBoolean return result.getJNIValue(in: environment) } @@ -376,7 +436,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) } """ diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 118ca789..73357d6b 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -168,7 +168,6 @@ final class MethodImportTests { } assertOutput( - dump: true, output, expected: """ @@ -212,7 +211,6 @@ final class MethodImportTests { } assertOutput( - dump: true, output, expected: """ @@ -256,7 +254,6 @@ final class MethodImportTests { } assertOutput( - dump: true, output, expected: """ diff --git a/Tests/JExtractSwiftTests/OptionalImportTests.swift b/Tests/JExtractSwiftTests/OptionalImportTests.swift index 756bb3d9..daa9fe66 100644 --- a/Tests/JExtractSwiftTests/OptionalImportTests.swift +++ b/Tests/JExtractSwiftTests/OptionalImportTests.swift @@ -47,8 +47,8 @@ final class OptionalImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment arg) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(arg); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(arg); } HANDLE.invokeExact(arg); } catch (Throwable ex$) { @@ -87,8 +87,8 @@ final class OptionalImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment arg) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(arg); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(arg); } HANDLE.invokeExact(arg); } catch (Throwable ex$) { @@ -128,8 +128,8 @@ final class OptionalImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment arg) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(arg); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(arg); } HANDLE.invokeExact(arg); } catch (Throwable ex$) { diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index 6a5c1c80..ea81ac84 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -45,8 +45,8 @@ final class StringPassingTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment string) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(string); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(string); } return (long) HANDLE.invokeExact(string); } catch (Throwable ex$) { diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index e0a6678c..da0c1afa 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -51,8 +51,8 @@ final class VariableImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment self) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(self); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(self); } return (long) HANDLE.invokeExact(self); } catch (Throwable ex$) { @@ -84,8 +84,8 @@ final class VariableImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long newValue, java.lang.foreign.MemorySegment self) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(newValue, self); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(newValue, self); } HANDLE.invokeExact(newValue, self); } catch (Throwable ex$) { From a82196e91377f09a7919a27809acffa25b217695 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 17 Jul 2025 15:51:01 +0900 Subject: [PATCH 103/178] Build: simple local maven publishing (#324) --- SwiftKitCore/build.gradle | 17 +++++++++++++++++ SwiftKitFFM/build.gradle | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 78cdbeb2..7bab76e0 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -14,15 +14,32 @@ plugins { id("build-logic.java-application-conventions") + id("maven-publish") } group = "org.swift.swiftkit" version = "1.0-SNAPSHOT" +base { + archivesName = "swiftkit-core" +} repositories { + mavenLocal() mavenCentral() } +publishing { + publications { + maven(MavenPublication) { + groupId = group + artifactId = 'swiftkit-core' + version = '1.0-SNAPSHOT' + + from components.java + } + } +} + java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index 22f88001..d818586d 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -14,15 +14,32 @@ plugins { id("build-logic.java-application-conventions") + id("maven-publish") } group = "org.swift.swiftkit" version = "1.0-SNAPSHOT" +base { + archivesName = "swiftkit-ffm" +} repositories { + mavenLocal() mavenCentral() } +publishing { + publications { + maven(MavenPublication) { + groupId = group + artifactId = 'swiftkit-ffm' + version = '1.0-SNAPSHOT' + + from components.java + } + } +} + java { toolchain { languageVersion.set(JavaLanguageVersion.of(24)) From 86da9eec8711415f142e5967d4352bb1352c77e1 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 22 Jul 2025 10:31:20 +0200 Subject: [PATCH 104/178] [JExtract/JNI] Fix boolean naming for variables already called `isX` (#325) --- .../Convenience/String+Extensions.swift | 3 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 18 +++- .../Asserts/TextAssertions.swift | 1 - .../JNI/JNIVariablesTests.swift | 87 ++++++++++++++++++- 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 1878ba2f..1851e154 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -14,8 +14,7 @@ extension String { - // TODO: naive implementation good enough for our simple case `methodMethodSomething` -> `MethodSomething` - var toCamelCase: String { + var firstCharacterUppercased: String { guard let f = first else { return self } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 4b8a478a..dd7e9c10 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -160,15 +160,25 @@ extension ImportedFunc { let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool if !returnsBoolean { - return "get\(self.name.toCamelCase)" + return "get\(self.name.firstCharacterUppercased)" } else if !self.name.hasJavaBooleanNamingConvention { - return "is\(self.name.toCamelCase)" + return "is\(self.name.firstCharacterUppercased)" } else { - return self.name.toCamelCase + return self.name } } var javaSetterName: String { - "set\(self.name.toCamelCase)" + let isBooleanSetter = self.functionSignature.parameters.first?.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + // If the variable is already named "isX", then we make + // the setter "setX" to match beans spec. + if isBooleanSetter && self.name.hasJavaBooleanNamingConvention { + // Safe to force unwrap due to `hasJavaBooleanNamingConvention` check. + let propertyName = self.name.split(separator: "is", maxSplits: 1).last! + return "set\(propertyName)" + } else { + return "set\(self.name.firstCharacterUppercased)" + } } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 5b70f68a..47397c63 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -115,7 +115,6 @@ func assertOutput( print("==== ---------------------------------------------------------------") #expect(output.contains(expectedChunk), sourceLocation: sourceLocation) - fatalError("Failed: \(filePath):\(line)") continue } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 3f65bd97..272d27b5 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -33,7 +33,7 @@ struct JNIVariablesTests { set { } } public var someBoolean: Bool - public let isBoolean: Bool + public var isBoolean: Bool } """ @@ -411,7 +411,7 @@ struct JNIVariablesTests { } @Test - func boolean_swiftThunks() throws { + func someBoolean_swiftThunks() throws { try assertOutput( input: membersSource, .jni, @@ -450,4 +450,87 @@ struct JNIVariablesTests { ] ) } + + @Test + func isBoolean_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var isBoolean: Bool + * } + */ + public boolean isBoolean() { + long self$ = this.$memoryAddress(); + return MyClass.$isBoolean(self$); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var isBoolean: Bool + * } + */ + public void setBoolean(boolean newValue) { + long self$ = this.$memoryAddress(); + MyClass.$setBoolean(newValue, self$); + } + """, + """ + private static native boolean $isBoolean(long selfPointer); + """, + """ + private static native void $setBoolean(boolean newValue, long selfPointer); + """ + ] + ) + } + + @Test + func isBoolean_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") + func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result = self$.pointee.isBoolean + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") + func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } } From a25d2166c2d9d21638166ae997aec60542d9d6ee Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 22 Jul 2025 12:40:04 +0200 Subject: [PATCH 105/178] [JExtract/JNI] Add support for classes/structs as parameters and return values (#326) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 12 +- .../com/example/swift/MySwiftClassTest.java | 21 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 127 +++++------ ...ISwift2JavaGenerator+JavaTranslation.swift | 204 +++++++++++++---- ...wift2JavaGenerator+NativeTranslation.swift | 209 ++++++++++++++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 181 ++++++--------- Sources/JExtractSwiftLib/JNI/JNIType.swift | 82 +++++++ .../JavaConstants/JavaTypes.swift | 10 + .../JNI/JNIClassTests.swift | 164 +++++++++++--- .../JNI/JNIModuleTests.swift | 81 ++++--- .../JNI/JNIStructTests.swift | 30 ++- .../JNI/JNIVariablesTests.swift | 179 ++++++--------- 12 files changed, 883 insertions(+), 417 deletions(-) create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNIType.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index fd0ce488..b4447f4f 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -13,8 +13,8 @@ //===----------------------------------------------------------------------===// public class MySwiftClass { - let x: Int64 - let y: Int64 + public let x: Int64 + public let y: Int64 public let byte: UInt8 = 0 public let constant: Int64 = 100 @@ -76,4 +76,12 @@ public class MySwiftClass { public func throwingFunction() throws { throw MySwiftClassError.swiftError } + + public func sumX(with other: MySwiftClass) -> Int64 { + return self.x + other.x + } + + public func copy() -> MySwiftClass { + return MySwiftClass(x: self.x, y: self.y) + } } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index bf244416..4425d6b2 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -118,4 +118,25 @@ void isWarm() { assertFalse(c.isWarm()); } } + + @Test + void sumWithX() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + MySwiftClass c2 = MySwiftClass.init(50, 10, arena); + assertEquals(70, c1.sumX(c2)); + } + } + + @Test + void copy() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + MySwiftClass c2 = c1.copy(arena); + + assertEquals(20, c2.getX()); + assertEquals(10, c2.getY()); + assertNotEquals(c1.$memoryAddress(), c2.$memoryAddress()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 4f4ad4d8..9c3d14b8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -117,7 +117,7 @@ extension JNISwift2JavaGenerator { printer.println() for initializer in decl.initializers { - printInitializerBindings(&printer, initializer, type: decl) + printFunctionBinding(&printer, initializer) printer.println() } @@ -176,75 +176,82 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let _ = translatedDecl(for: decl) else { + guard let translatedDecl = translatedDecl(for: decl) else { // Failed to translate. Skip. return } + var modifiers = ["public"] if decl.isStatic || decl.isInitializer || !decl.hasParent { - printStaticFunctionBinding(&printer, decl) - } else { - printMemberMethodBindings(&printer, decl) + modifiers.append("static") } - } - private func printStaticFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - printDeclDocumentation(&printer, decl) - printer.print( - "public static native \(renderFunctionSignature(decl));" - ) - } + let translatedSignature = translatedDecl.translatedFunctionSignature + let resultType = translatedSignature.resultType.javaType + var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.asParameter) + if translatedSignature.requiresSwiftArena { + parameters.append("SwiftArena swiftArena$") + } + let throwsClause = decl.isThrowing ? " throws Exception" : "" - /// Renders Java bindings for member methods - /// - /// Member methods are generated as a function that extracts the `selfPointer` - /// and passes it down to another native function along with the arguments - /// to call the Swift implementation. - private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. + let modifiersStr = modifiers.joined(separator: " ") + let parametersStr = parameters.joined(separator: ", ") printDeclDocumentation(&printer, decl) - printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in - var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) - - let selfVarName = "self$" - arguments.append(selfVarName) + printer.printBraceBlock( + "\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + ) { printer in + printDowncall(&printer, decl) + } - let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return " + printNativeFunction(&printer, decl) + } - printer.print( - """ - long \(selfVarName) = this.$memoryAddress(); - \(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", "))); - """ - ) + private func printNativeFunction(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl + let nativeSignature = translatedDecl.nativeFunctionSignature + let resultType = nativeSignature.result.javaType + var parameters = nativeSignature.parameters + if let selfParameter = nativeSignature.selfParameter { + parameters.append(selfParameter) } + let renderedParameters = parameters.map { "\($0.javaParameter.type) \($0.javaParameter.name)"}.joined(separator: ", ") - let returnType = translatedDecl.translatedFunctionSignature.resultType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) - parameters.append("long selfPointer") - printer.print("private static native \(returnType) $\(translatedDecl.name)(\(parameters.joined(separator: ", ")));") + printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));") } - private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { - guard let translatedDecl = translatedDecl(for: decl) else { - // Failed to translate. Skip. - return + private func printDowncall( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. + let translatedFunctionSignature = translatedDecl.translatedFunctionSignature + + // Regular parameters. + var arguments = [String]() + for parameter in translatedFunctionSignature.parameters { + let lowered = parameter.conversion.render(&printer, parameter.parameter.name) + arguments.append(lowered) } - printDeclDocumentation(&printer, decl) - printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in - let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) - printer.print( - """ - long self$ = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); - return new \(type.qualifiedName)(self$, swiftArena$); - """ - ) + // 'self' parameter. + if let selfParameter = translatedFunctionSignature.selfParameter { + let lowered = selfParameter.conversion.render(&printer, "this") + arguments.append(lowered) } - let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) - printer.print("private static native long allocatingInit(\(parameters.joined(separator: ", ")));") + //=== Part 3: Downcall. + // TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names + // using the registry? + let downcall = "\(translatedDecl.parentName).\(translatedDecl.nativeFunctionName)(\(arguments.joined(separator: ", ")))" + + //=== Part 4: Convert the return value. + if translatedFunctionSignature.resultType.javaType.isVoid { + printer.print("\(downcall);") + } else { + let result = translatedFunctionSignature.resultType.conversion.render(&printer, downcall) + printer.print("return \(result);") + } } private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { @@ -288,24 +295,4 @@ extension JNISwift2JavaGenerator { ) } } - - /// Renders a Java function signature - /// - /// `func method(x: Int, y: Int) -> Int` becomes - /// `long method(long x, long y)` - private func renderFunctionSignature(_ decl: ImportedFunc) -> String { - guard let translatedDecl = translatedDecl(for: decl) else { - fatalError("Unable to render function signature for a function that cannot be translated: \(decl)") - } - let resultType = translatedDecl.translatedFunctionSignature.resultType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) - - if decl.isInitializer { - parameters.append("SwiftArena swiftArena$") - } - - let throwsClause = decl.isThrowing ? " throws Exception" : "" - - return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" - } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 2e3ffff8..e0253624 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -39,7 +39,16 @@ extension JNISwift2JavaGenerator { let swiftModuleName: String func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { + let nativeTranslation = NativeJavaTranslation() + + // Swift -> Java let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature) + // Java -> Java (native) + let nativeFunctionSignature = try nativeTranslation.translate( + functionSignature: decl.functionSignature, + translatedFunctionSignature: translatedFunctionSignature + ) + // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -52,68 +61,99 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, + nativeFunctionName: "$\(javaName)", parentName: parentName, - translatedFunctionSignature: translatedFunctionSignature + translatedFunctionSignature: translatedFunctionSignature, + nativeFunctionSignature: nativeFunctionSignature ) } - func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) throws -> TranslatedFunctionSignature { + func translate(functionSignature: SwiftFunctionSignature) throws -> TranslatedFunctionSignature { let parameters = try functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" - return try translate(swiftParam: param, parameterName: parameterName) + return try translateParameter(swiftType: param.type, parameterName: parameterName) + } + + // 'self' + let selfParameter: TranslatedParameter? + if case .instance(let swiftSelf) = functionSignature.selfParameter { + selfParameter = try self.translateParameter( + swiftType: swiftSelf.type, + parameterName: swiftSelf.parameterName ?? "self" + ) + } else { + selfParameter = nil } return try TranslatedFunctionSignature( + selfParameter: selfParameter, parameters: parameters, - resultType: translate(swiftType: functionSignature.result.type) - ) - } - - func translate(swiftParam: SwiftParameter, parameterName: String) throws -> JavaParameter { - return try JavaParameter( - name: parameterName, - type: translate(swiftType: swiftParam.type) + resultType: translate(swiftResult: functionSignature.result) ) } - func translate(swiftType: SwiftType) throws -> JavaType { + func translateParameter(swiftType: SwiftType, parameterName: String) throws -> TranslatedParameter { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = translate(knownType: knownType) else { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - return javaType + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: javaType), + conversion: .placeholder + ) } - return .class(package: nil, name: nominalType.nominalTypeDecl.name) + // For now, we assume this is a JExtract class. + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: nominalType.nominalTypeDecl.name) + ), + conversion: .valueMemoryAddress(.placeholder) + ) case .tuple([]): - return .void + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .void), + conversion: .placeholder + ) case .metatype, .optional, .tuple, .function, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } - func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { - switch knownType { - case .bool: .boolean - 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 .string: .javaLangString - case .int, .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: - nil + func translate( + swiftResult: SwiftResult + ) throws -> TranslatedResult { + switch swiftResult.type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return TranslatedResult( + javaType: javaType, + conversion: .placeholder + ) + } + + // For now, we assume this is a JExtract class. + let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) + return TranslatedResult( + javaType: javaType, + conversion: .constructSwiftValue(.placeholder, javaType) + ) + + case .tuple([]): + return TranslatedResult(javaType: .void, conversion: .placeholder) + + case .metatype, .optional, .tuple, .function, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } } @@ -122,16 +162,106 @@ extension JNISwift2JavaGenerator { /// Java function name let name: String + /// The name of the native function + let nativeFunctionName: String + /// The name of the Java parent scope this function is declared in let parentName: String - /// Function signature + /// Function signature of the Java function the user will call let translatedFunctionSignature: TranslatedFunctionSignature + + /// Function signature of the native function that will be implemented by Swift + let nativeFunctionSignature: NativeFunctionSignature + } + + static func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { + switch knownType { + case .bool: .boolean + 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 .string: .javaLangString + case .int, .uint, .uint8, .uint32, .uint64, + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: + nil + } } struct TranslatedFunctionSignature { - let parameters: [JavaParameter] - let resultType: JavaType + let selfParameter: TranslatedParameter? + let parameters: [TranslatedParameter] + let resultType: TranslatedResult + + var requiresSwiftArena: Bool { + return self.resultType.conversion.requiresSwiftArena + } + } + + /// Represent a Swift API parameter translated to Java. + struct TranslatedParameter { + let parameter: JavaParameter + let conversion: JavaNativeConversionStep + } + + /// Represent a Swift API result translated to Java. + struct TranslatedResult { + let javaType: JavaType + + /// Represents how to convert the Java native result into a user-facing result. + let conversion: JavaNativeConversionStep + } + + /// Describes how to convert values between Java types and the native Java function + enum JavaNativeConversionStep { + /// The value being converted + case placeholder + + /// `value.$memoryAddress()` + indirect case valueMemoryAddress(JavaNativeConversionStep) + + /// Call `new \(Type)(\(placeholder), swiftArena$)` + indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + + /// 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 .placeholder: + return placeholder + + case .valueMemoryAddress: + return "\(placeholder).$memoryAddress()" + + case .constructSwiftValue(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "new \(javaType.className!)(\(inner), swiftArena$)" + + } + } + + /// Whether the conversion uses SwiftArena. + var requiresSwiftArena: Bool { + switch self { + case .placeholder: + return false + + case .constructSwiftValue: + return true + + case .valueMemoryAddress(let inner): + return inner.requiresSwiftArena + } + } } enum JavaTranslationError: Error { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift new file mode 100644 index 00000000..461c7301 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -0,0 +1,209 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +extension JNISwift2JavaGenerator { + + struct NativeJavaTranslation { + /// Translates a Swift function into the native JNI method signature. + func translate( + functionSignature: SwiftFunctionSignature, + translatedFunctionSignature: TranslatedFunctionSignature + ) throws -> NativeFunctionSignature { + let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { translatedParameter, swiftParameter in + let parameterName = translatedParameter.parameter.name + return try translate(swiftParameter: swiftParameter, parameterName: parameterName) + } + + // Lower the self parameter. + let nativeSelf: NativeParameter? = switch functionSignature.selfParameter { + case .instance(let selfParameter): + try translate( + swiftParameter: selfParameter, + parameterName: selfParameter.parameterName ?? "self" + ) + case nil, .initializer(_), .staticMethod(_): + nil + } + + return try NativeFunctionSignature( + selfParameter: nativeSelf, + parameters: parameters, + result: translate(swiftResult: functionSignature.result) + ) + } + + func translate( + swiftParameter: SwiftParameter, + parameterName: String + ) throws -> NativeParameter { + switch swiftParameter.type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + + return NativeParameter( + javaParameter: JavaParameter(name: parameterName, type: javaType), + conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) + ) + } + + case .tuple([]): + return NativeParameter( + javaParameter: JavaParameter(name: parameterName, type: .void), + conversion: .placeholder + ) + + case .metatype, .optional, .tuple, .function, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + + // Classes are passed as the pointer. + return NativeParameter( + javaParameter: JavaParameter(name: parameterName, type: .long), + conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) + ) + } + + func translate( + swiftResult: SwiftResult + ) throws -> NativeResult { + switch swiftResult.type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return NativeResult( + javaType: javaType, + conversion: .getJNIValue(.placeholder) + ) + } + + case .tuple([]): + return NativeResult( + javaType: .void, + conversion: .placeholder + ) + + case .metatype, .optional, .tuple, .function, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + // TODO: Handle other classes, for example from JavaKit macros. + // for now we assume all passed in classes are JExtract generated + // so we pass the pointer. + return NativeResult( + javaType: .long, + conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) + ) + } + } + + struct NativeFunctionSignature { + let selfParameter: NativeParameter? + let parameters: [NativeParameter] + let result: NativeResult + } + + struct NativeParameter { + let javaParameter: JavaParameter + + var jniType: JNIType { + javaParameter.type.jniType + } + + /// Represents how to convert the JNI parameter to a Swift parameter + let conversion: NativeSwiftConversionStep + } + + struct NativeResult { + let javaType: JavaType + let conversion: NativeSwiftConversionStep + } + + /// Describes how to convert values between Java types and Swift through JNI + enum NativeSwiftConversionStep { + /// The value being converted + case placeholder + + /// `value.getJNIValue(in:)` + indirect case getJNIValue(NativeSwiftConversionStep) + + /// `SwiftType(from: value, in: environment!)` + indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) + + /// Extracts a swift type at a pointer given by a long. + indirect case extractSwiftValue(NativeSwiftConversionStep, swiftType: SwiftType) + + /// Allocate memory for a Swift value and outputs the pointer + indirect case allocateSwiftValue(name: String, 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(NativeSwiftConversionStep) + + + /// 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 .placeholder: + return placeholder + + case .getJNIValue(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).getJNIValue(in: environment!)" + + case .initFromJNI(let inner, let swiftType): + let inner = inner.render(&printer, placeholder) + return "\(swiftType)(fromJNI: \(inner), in: environment!)" + + case .extractSwiftValue(let inner, let swiftType): + let inner = inner.render(&printer, placeholder) + printer.print( + """ + assert(\(inner) != 0, "\(inner) memory address was null") + let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) + guard let \(inner)$ = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) else { + fatalError("\(inner) memory address was null in call to \\(#function)!") + } + """ + ) + return "\(inner)$" + + case .allocateSwiftValue(let name, let swiftType): + let pointerName = "\(name)$" + let bitsName = "\(name)Bits$" + printer.print( + """ + let \(pointerName) = UnsafeMutablePointer<\(swiftType)>.allocate(capacity: 1) + \(pointerName).initialize(to: \(placeholder)) + let \(bitsName) = Int64(Int(bitPattern: \(pointerName))) + """ + ) + return bitsName + + case .pointee(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).pointee" + } + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 38d4ff79..d8b6b275 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -98,7 +98,7 @@ extension JNISwift2JavaGenerator { printHeader(&printer) for initializer in type.initializers { - printInitializerThunk(&printer, initializer) + printSwiftFunctionThunk(&printer, initializer) printer.println() } @@ -115,141 +115,105 @@ extension JNISwift2JavaGenerator { printDestroyFunctionThunk(&printer, type) } - private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let translatedDecl = translatedDecl(for: decl) else { - // Failed to translate. Skip. - return - } - - let typeName = translatedDecl.parentName - - printCDecl( - &printer, - javaMethodName: "allocatingInit", - parentName: translatedDecl.parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters, - isStatic: true, - resultType: .long - ) { printer in - let downcallArguments = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature - ) - // TODO: Throwing initializers - printer.print( - """ - let self$ = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) - self$.initialize(to: \(typeName)(\(downcallArguments))) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) - """ - ) - } - } - private func printSwiftFunctionThunk( _ printer: inout CodePrinter, _ decl: ImportedFunc ) { - guard let _ = translatedDecl(for: decl) else { + guard let translatedDecl = translatedDecl(for: decl) else { // Failed to translate. Skip. return } - // Free functions does not have a parent - if decl.isStatic || !decl.hasParent { - self.printSwiftStaticFunctionThunk(&printer, decl) - } else { - self.printSwiftMemberFunctionThunk(&printer, decl) - } - } + let nativeSignature = translatedDecl.nativeFunctionSignature + var parameters = nativeSignature.parameters - private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if we can translate the decl. - - printCDecl( - &printer, - javaMethodName: translatedDecl.name, - parentName: translatedDecl.parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters, - isStatic: true, - resultType: translatedDecl.translatedFunctionSignature.resultType - ) { printer in - // For free functions the parent is the Swift module - let parentName = decl.parentType?.asNominalTypeDeclaration?.qualifiedName ?? swiftModuleName - self.printFunctionDowncall(&printer, decl, calleeName: parentName) + if let selfParameter = nativeSignature.selfParameter { + parameters.append(selfParameter) } - } - - private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if can translate the decl. - let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName - let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( &printer, - javaMethodName: "$\(translatedDecl.name)", + javaMethodName: translatedDecl.nativeFunctionName, parentName: translatedDecl.parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters + [ - selfPointerParam - ], - isStatic: true, - resultType: translatedDecl.translatedFunctionSignature.resultType + parameters: parameters.map(\.javaParameter), + resultType: nativeSignature.result.javaType.jniType ) { printer in - let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, - swiftParentName: swiftParentName, selfPointerParam) - self.printFunctionDowncall(&printer, decl, calleeName: "\(selfVar).pointee") + self.printFunctionDowncall(&printer, decl) } } private func printFunctionDowncall( _ printer: inout CodePrinter, - _ decl: ImportedFunc, - calleeName: String + _ decl: ImportedFunc ) { guard let translatedDecl = self.translatedDecl(for: decl) else { fatalError("Cannot print function downcall for a function that can't be translated: \(decl)") } - let swiftReturnType = decl.functionSignature.result.type + let nativeSignature = translatedDecl.nativeFunctionSignature let tryClause: String = decl.isThrowing ? "try " : "" + // Regular parameters. + var arguments = [String]() + for parameter in nativeSignature.parameters { + let lowered = parameter.conversion.render(&printer, parameter.javaParameter.name) + arguments.append(lowered) + } + + // Callee + let callee: String = switch decl.functionSignature.selfParameter { + case .instance(let swiftSelf): + nativeSignature.selfParameter!.conversion.render( + &printer, + swiftSelf.parameterName ?? "self" + ) + case .staticMethod(let selfType), .initializer(let selfType): + "\(selfType)" + case .none: + swiftModuleName + } + + // Build the result let result: String switch decl.apiKind { case .function, .initializer: - let downcallParameters = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature - ) - result = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + let downcallArguments = zip( + decl.functionSignature.parameters, + arguments + ).map { originalParam, argument in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(argument)" + } + .joined(separator: ", ") + result = "\(tryClause)\(callee).\(decl.name)(\(downcallArguments))" case .getter: - result = "\(tryClause)\(calleeName).\(decl.name)" + result = "\(tryClause)\(callee).\(decl.name)" case .setter: - guard let newValueParameter = decl.functionSignature.parameters.first else { + guard let newValueArgument = arguments.first else { fatalError("Setter did not contain newValue parameter: \(decl)") } - result = "\(calleeName).\(decl.name) = \(renderJNIToSwiftConversion("newValue", type: newValueParameter.type))" + result = "\(callee).\(decl.name) = \(newValueArgument)" } - let returnStatement = - if swiftReturnType.isVoid { - result + // Lower the result. + let innerBody: String + if !decl.functionSignature.result.type.isVoid { + let loweredResult = nativeSignature.result.conversion.render(&printer, result) + innerBody = "return \(loweredResult)" } else { - """ - let result = \(result) - return result.getJNIValue(in: environment) - """ + innerBody = result } if decl.isThrowing { - let dummyReturn = - !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" + // TODO: Handle classes for dummy value + let dummyReturn = !nativeSignature.result.javaType.isVoid ? "return \(decl.functionSignature.result.type).jniPlaceholderValue" : "" printer.print( """ do { - \(returnStatement) + \(innerBody) } catch { environment.throwAsException(error) \(dummyReturn) @@ -257,7 +221,7 @@ extension JNISwift2JavaGenerator { """ ) } else { - printer.print(returnStatement) + printer.print(innerBody) } } @@ -266,8 +230,7 @@ extension JNISwift2JavaGenerator { javaMethodName: String, parentName: String, parameters: [JavaParameter], - isStatic: Bool, - resultType: JavaType, + resultType: JNIType, _ body: (inout CodePrinter) -> Void ) { let jniSignature = parameters.reduce(into: "") { signature, parameter in @@ -283,16 +246,15 @@ extension JNISwift2JavaGenerator { + jniSignature.escapedJNIIdentifier let translatedParameters = parameters.map { - "\($0.name): \($0.type.jniTypeName)" + "\($0.name): \($0.type.jniType)" } - let thisParameter = isStatic ? "thisClass: jclass" : "thisObject: jobject" let thunkParameters = [ "environment: UnsafeMutablePointer!", - thisParameter + "thisClass: jclass" ] + translatedParameters - let thunkReturnType = !resultType.isVoid ? " -> \(resultType.jniTypeName)" : "" + let thunkReturnType = resultType != .void ? " -> \(resultType)" : "" // TODO: Think about function overloads printer.printBraceBlock( @@ -326,7 +288,6 @@ extension JNISwift2JavaGenerator { parameters: [ selfPointerParam ], - isStatic: true, resultType: .void ) { printer in let parentName = type.qualifiedName @@ -347,7 +308,9 @@ extension JNISwift2JavaGenerator { /// - Returns: name of the created "self" variable private func printSelfJLongToUnsafeMutablePointer( _ printer: inout CodePrinter, - swiftParentName: String, _ selfPointerParam: JavaParameter) -> String { + swiftParentName: String, + _ selfPointerParam: JavaParameter + ) -> String { let newSelfParamName = "self$" printer.print( """ @@ -363,26 +326,6 @@ extension JNISwift2JavaGenerator { ) return newSelfParamName } - - - /// Renders the arguments for making a downcall - private func renderDowncallArguments( - swiftFunctionSignature: SwiftFunctionSignature, - translatedFunctionSignature: TranslatedFunctionSignature - ) -> String { - zip( - swiftFunctionSignature.parameters, - translatedFunctionSignature.parameters - ).map { originalParam, translatedParam in - let label = originalParam.argumentLabel.map { "\($0): " } ?? "" - return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.name), in: environment!)" - } - .joined(separator: ", ") - } - - private func renderJNIToSwiftConversion(_ variableName: String, type: SwiftType) -> String { - "\(type)(fromJNI: \(variableName), in: environment!)" - } } extension String { diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift new file mode 100644 index 00000000..d152942a --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNIType.swift @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +/// Represents types that are able to be passed over a JNI boundary. +/// +/// - SeeAlso: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html +enum JNIType { + case jboolean + case jfloat + case jdouble + case jbyte + case jchar + case jshort + case jint + case jlong + case void + case jstring + case jclass + case jthrowable + case jobject + case jbooleanArray + case jbyteArray + case jcharArray + case jshortArray + case jintArray + case jlongArray + case jfloatArray + case jdoubleArray + case jobjectArray +} + +extension JavaType { + var jniType: JNIType { + switch self { + case .boolean: .jboolean + case .byte: .jbyte + case .char: .jchar + case .short: .jshort + case .int: .jint + case .long: .jlong + case .float: .jfloat + case .double: .jdouble + case .void: .void + case .array(.boolean): .jbooleanArray + case .array(.byte): .jbyteArray + case .array(.char): .jcharArray + case .array(.short): .jshortArray + case .array(.int): .jintArray + case .array(.long): .jlongArray + case .array(.float): .jfloatArray + case .array(.double): .jdoubleArray + case .array: .jobjectArray + case .javaLangString: .jstring + case .javaLangClass: .jclass + case .javaLangThrowable: .jthrowable + case .class: .jobject + } + } + + /// Returns whether this type returns `JavaValue` from JavaKit + var implementsJavaValue: Bool { + switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: + true + default: + false + } + } +} diff --git a/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift b/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift index 633943ef..02850801 100644 --- a/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift +++ b/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift @@ -29,4 +29,14 @@ extension JavaType { static var javaLangRunnable: JavaType { .class(package: "java.lang", name: "Runnable") } + + /// The description of the type java.lang.Class. + static var javaLangClass: JavaType { + .class(package: "java.lang", name: "Class") + } + + /// The description of the type java.lang.Throwable. + static var javaLangThrowable: JavaType { + .class(package: "java.lang", name: "Throwable") + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 5970e7a0..cfe78fbd 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -35,6 +35,9 @@ struct JNIClassTests { } public func doSomething(x: Int64) {} + + public func copy() -> MyClass {} + public func isEqual(to other: MyClass) -> Bool {} } """ @@ -103,7 +106,12 @@ struct JNIClassTests { * public static func method() * } */ - public static native void method(); + public static void method() { + MyClass.$method(); + } + """, + """ + private static native void $method(); """ ] ) @@ -118,8 +126,8 @@ struct JNIClassTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyClass_method__") - func Java_com_example_swift_MyClass_method__(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_MyClass__00024method__") + func Java_com_example_swift_MyClass__00024method__(environment: UnsafeMutablePointer!, thisClass: jclass) { MyClass.method() } """ @@ -142,8 +150,7 @@ struct JNIClassTests { * } */ public static MyClass init(long x, long y, SwiftArena swiftArena$) { - long self$ = MyClass.allocatingInit(x, y); - return new MyClass(self$, swiftArena$); + return new MyClass(MyClass.$init(x, y), swiftArena$); } """, """ @@ -154,15 +161,14 @@ struct JNIClassTests { * } */ public static MyClass init(SwiftArena swiftArena$) { - long self$ = MyClass.allocatingInit(); - return new MyClass(self$, swiftArena$); + return new MyClass(MyClass.$init(), swiftArena$); } """, """ - private static native long allocatingInit(long x, long y); + private static native long $init(long x, long y); """, """ - private static native long allocatingInit(); + private static native long $init(); """ ] ) @@ -177,19 +183,21 @@ struct JNIClassTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyClass_allocatingInit__") - func Java_com_example_swift_MyClass_allocatingInit__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { - let self$ = UnsafeMutablePointer.allocate(capacity: 1) - self$.initialize(to: MyClass()) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_MyClass__00024init__JJ") + func Java_com_example_swift_MyClass__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) } """, """ - @_cdecl("Java_com_example_swift_MyClass_allocatingInit__JJ") - func Java_com_example_swift_MyClass_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let self$ = UnsafeMutablePointer.allocate(capacity: 1) - self$.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_MyClass__00024init__") + func Java_com_example_swift_MyClass__00024init__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyClass.init()) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) } """ ] @@ -238,12 +246,11 @@ struct JNIClassTests { * } */ public void doSomething(long x) { - long self$ = this.$memoryAddress(); - MyClass.$doSomething(x, self$); + MyClass.$doSomething(x, this.$memoryAddress()); } """, """ - private static native void $doSomething(long x, long selfPointer); + private static native void $doSomething(long x, long self); """ ] ) @@ -259,12 +266,9 @@ struct JNIClassTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") - func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } @@ -274,4 +278,108 @@ struct JNIClassTests { ] ) } + + @Test + func methodReturningClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func copy() -> MyClass + * } + */ + public MyClass copy(SwiftArena swiftArena$) { + return new MyClass(MyClass.$copy(this.$memoryAddress()), swiftArena$); + } + """, + """ + private static native long $copy(long self); + """ + ] + ) + } + + @Test + func methodReturningClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024copy__J") + func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: self$.pointee.copy()) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """, + ] + ) + } + + @Test + func classAsParameter_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func isEqual(to other: MyClass) -> Bool + * } + */ + public boolean isEqual(MyClass other) { + return MyClass.$isEqual(other.$memoryAddress(), this.$memoryAddress()); + } + """, + """ + private static native boolean $isEqual(long other, long self); + """ + ] + ) + } + + @Test + func classAsParameter_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024isEqual__JJ") + func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { + assert(other != 0, "other memory address was null") + let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) + guard let other$ = UnsafeMutablePointer(bitPattern: otherBits$) else { + fatalError("other memory address was null in call to \\(#function)!") + } + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) + } + """, + ] + ) + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index d6a030a8..4696253c 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -67,7 +67,12 @@ struct JNIModuleTests { * public func helloWorld() * } */ - public static native void helloWorld(); + public static void helloWorld() { + SwiftModule.$helloWorld(); + } + """, + """ + private static native void $helloWorld(); """, """ /** @@ -76,7 +81,12 @@ struct JNIModuleTests { * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ - public static native char takeIntegers(byte i1, short i2, int i3, long i4); + public static char takeIntegers(byte i1, short i2, int i3, long i4) { + return SwiftModule.$takeIntegers(i1, i2, i3, i4); + } + """, + """ + private static native char $takeIntegers(byte i1, short i2, int i3, long i4); """, """ /** @@ -85,7 +95,12 @@ struct JNIModuleTests { * public func otherPrimitives(b: Bool, f: Float, d: Double) * } */ - public static native void otherPrimitives(boolean b, float f, double d); + public static void otherPrimitives(boolean b, float f, double d) { + SwiftModule.$otherPrimitives(b, f, d); + } + """, + """ + private static native void $otherPrimitives(boolean b, float f, double d); """ ] ) @@ -100,21 +115,20 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_helloWorld__") - func Java_com_example_swift_SwiftModule_helloWorld__(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule__00024helloWorld__") + func Java_com_example_swift_SwiftModule__00024helloWorld__(environment: UnsafeMutablePointer!, thisClass: jclass) { SwiftModule.helloWorld() } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers__BSIJ") - func Java_com_example_swift_SwiftModule_takeIntegers__BSIJ(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: Int64(fromJNI: i4, in: environment!)) - return result.getJNIValue(in: environment) - } + @_cdecl("Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ") + func Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { + return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)).getJNIValue(in: environment!) + } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives__ZFD") - func Java_com_example_swift_SwiftModule_otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { + @_cdecl("Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD") + func Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD(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!)) } """ @@ -136,8 +150,13 @@ struct JNIModuleTests { * public func copy(_ string: String) -> String * } */ - public static native java.lang.String copy(java.lang.String string); + public static java.lang.String copy(java.lang.String string) { + return SwiftModule.$copy(string); + } """, + """ + private static native java.lang.String $copy(java.lang.String string); + """ ] ) } @@ -151,10 +170,9 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2") - func Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { - let result = SwiftModule.copy(String(fromJNI: string, in: environment!)) - return result.getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") + func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring) -> jstring { + return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) } """, ] @@ -175,7 +193,12 @@ struct JNIModuleTests { * public func methodA() throws * } */ - public static native void methodA() throws Exception; + public static void methodA() throws Exception { + SwiftModule.$methodA(); + } + """, + """ + private static native void $methodA(); """, """ /** @@ -184,8 +207,13 @@ struct JNIModuleTests { * public func methodB() throws -> Int64 * } */ - public static native long methodB() throws Exception; + public static long methodB() throws Exception { + return SwiftModule.$methodB(); + } """, + """ + private static native long $methodB(); + """ ] ) } @@ -199,9 +227,9 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_methodA__") - func Java_com_example_swift_SwiftModule_methodA__(environment: UnsafeMutablePointer!, thisClass: jclass) { - do { + @_cdecl("Java_com_example_swift_SwiftModule__00024methodA__") + func Java_com_example_swift_SwiftModule__00024methodA__(environment: UnsafeMutablePointer!, thisClass: jclass) { + do { try SwiftModule.methodA() } catch { environment.throwAsException(error) @@ -209,11 +237,10 @@ struct JNIModuleTests { } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_methodB__") - func Java_com_example_swift_SwiftModule_methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { - do { - let result = try SwiftModule.methodB() - return result.getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_SwiftModule__00024methodB__") + func Java_com_example_swift_SwiftModule__00024methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + do { + return try SwiftModule.methodB().getJNIValue(in: environment!) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 47b1c4dc..a4084654 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -98,12 +98,11 @@ struct JNIStructTests { * } */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { - long self$ = MyStruct.allocatingInit(x, y); - return new MyStruct(self$, swiftArena$); + return new MyStruct(MyStruct.$init(x, y), swiftArena$); } """, """ - private static native long allocatingInit(long x, long y); + private static native long $init(long x, long y); """, ] ) @@ -118,11 +117,12 @@ struct JNIStructTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyStruct_allocatingInit__JJ") - func Java_com_example_swift_MyStruct_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let self$ = UnsafeMutablePointer.allocate(capacity: 1) - self$.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_MyStruct__00024init__JJ") + func Java_com_example_swift_MyStruct__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) } """ ] @@ -170,12 +170,11 @@ struct JNIStructTests { * } */ public void doSomething(long x) { - long self$ = this.$memoryAddress(); - MyStruct.$doSomething(x, self$); + MyStruct.$doSomething(x, this.$memoryAddress()); } """, """ - private static native void $doSomething(long x, long selfPointer); + private static native void $doSomething(long x, long self); """ ] ) @@ -191,12 +190,9 @@ struct JNIStructTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") - func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 272d27b5..5757e8da 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -48,12 +48,11 @@ struct JNIVariablesTests { * } */ public long getConstant() { - long self$ = this.$memoryAddress(); - return MyClass.$getConstant(self$); + return MyClass.$getConstant(this.$memoryAddress()); } """, """ - private static native long $getConstant(long selfPointer); + private static native long $getConstant(long self); """ ]) } @@ -68,17 +67,13 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") - func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.constant - return result.getJNIValue(in: environment) + return self$.pointee.constant.getJNIValue(in: environment!) } """ ] @@ -101,8 +96,7 @@ struct JNIVariablesTests { * } */ public long getMutable() { - long self$ = this.$memoryAddress(); - return MyClass.$getMutable(self$); + return MyClass.$getMutable(this.$memoryAddress()); } """, """ @@ -113,15 +107,14 @@ struct JNIVariablesTests { * } */ public void setMutable(long newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setMutable(newValue, self$); + MyClass.$setMutable(newValue, this.$memoryAddress()); } """, """ - private static native long $getMutable(long selfPointer); + private static native long $getMutable(long self); """, """ - private static native void $setMutable(long newValue, long selfPointer); + private static native void $setMutable(long newValue, long self); """ ] ) @@ -137,27 +130,20 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") - func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.mutable - return result.getJNIValue(in: environment) + return self$.pointee.mutable.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") - func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } @@ -184,12 +170,11 @@ struct JNIVariablesTests { * } */ public long getComputed() { - long self$ = this.$memoryAddress(); - return MyClass.$getComputed(self$); + return MyClass.$getComputed(this.$memoryAddress()); } """, """ - private static native long $getComputed(long selfPointer); + private static native long $getComputed(long self); """, ] ) @@ -205,18 +190,13 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") - func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - - let result = self$.pointee.computed - return result.getJNIValue(in: environment) + return self$.pointee.computed.getJNIValue(in: environment!) } """, ] @@ -239,12 +219,11 @@ struct JNIVariablesTests { * } */ public long getComputedThrowing() throws Exception { - long self$ = this.$memoryAddress(); - return MyClass.$getComputedThrowing(self$); + return MyClass.$getComputedThrowing(this.$memoryAddress()); } """, """ - private static native long $getComputedThrowing(long selfPointer); + private static native long $getComputedThrowing(long self); """, ] ) @@ -260,19 +239,14 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") - func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - do { - let result = try self$.pointee.computedThrowing - return result.getJNIValue(in: environment) + return try self$.pointee.computedThrowing.getJNIValue(in: environment!) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue @@ -299,8 +273,7 @@ struct JNIVariablesTests { * } */ public long getGetterAndSetter() { - long self$ = this.$memoryAddress(); - return MyClass.$getGetterAndSetter(self$); + return MyClass.$getGetterAndSetter(this.$memoryAddress()); } """, """ @@ -311,15 +284,14 @@ struct JNIVariablesTests { * } */ public void setGetterAndSetter(long newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setGetterAndSetter(newValue, self$); + MyClass.$setGetterAndSetter(newValue, this.$memoryAddress()); } """, """ - private static native long $getGetterAndSetter(long selfPointer); + private static native long $getGetterAndSetter(long self); """, """ - private static native void $setGetterAndSetter(long newValue, long selfPointer); + private static native void $setGetterAndSetter(long newValue, long self); """ ] ) @@ -335,32 +307,23 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") - func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - - let result = self$.pointee.getterAndSetter - return result.getJNIValue(in: environment) + return self$.pointee.getterAndSetter.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") - func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) } """ @@ -384,8 +347,7 @@ struct JNIVariablesTests { * } */ public boolean isSomeBoolean() { - long self$ = this.$memoryAddress(); - return MyClass.$isSomeBoolean(self$); + return MyClass.$isSomeBoolean(this.$memoryAddress()); } """, """ @@ -396,15 +358,14 @@ struct JNIVariablesTests { * } */ public void setSomeBoolean(boolean newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setSomeBoolean(newValue, self$); + MyClass.$setSomeBoolean(newValue, this.$memoryAddress()); } """, """ - private static native boolean $isSomeBoolean(long selfPointer); + private static native boolean $isSomeBoolean(long self); """, """ - private static native void $setSomeBoolean(boolean newValue, long selfPointer); + private static native void $setSomeBoolean(boolean newValue, long self); """ ] ) @@ -420,27 +381,20 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") - func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.someBoolean - return result.getJNIValue(in: environment) + return self$.pointee.someBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") - func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } @@ -467,8 +421,7 @@ struct JNIVariablesTests { * } */ public boolean isBoolean() { - long self$ = this.$memoryAddress(); - return MyClass.$isBoolean(self$); + return MyClass.$isBoolean(this.$memoryAddress()); } """, """ @@ -479,15 +432,14 @@ struct JNIVariablesTests { * } */ public void setBoolean(boolean newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setBoolean(newValue, self$); + MyClass.$setBoolean(newValue, this.$memoryAddress()); } """, """ - private static native boolean $isBoolean(long selfPointer); + private static native boolean $isBoolean(long self); """, """ - private static native void $setBoolean(boolean newValue, long selfPointer); + private static native void $setBoolean(boolean newValue, long self); """ ] ) @@ -503,27 +455,20 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") - func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.isBoolean - return result.getJNIValue(in: environment) + return self$.pointee.isBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") - func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } From f62a31752f9840f2fb3fa78b9720e179404000e7 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 22 Jul 2025 13:38:15 +0200 Subject: [PATCH 106/178] [JExtract/JNI] Add support for primitive non-escaping closures. (#327) --- .../Sources/MySwiftLibrary/Closures.swift | 31 ++++ .../java/com/example/swift/ClosuresTest.java | 44 +++++ .../Convenience/JavaType+Extensions.swift | 23 +-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 71 +++++++- ...ISwift2JavaGenerator+JavaTranslation.swift | 114 ++++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 169 ++++++++++++++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 5 +- Sources/JExtractSwiftLib/JNI/JNIType.swift | 16 ++ Sources/JavaKit/JavaEnvironment.swift | 2 +- Sources/JavaKit/JavaValue.swift | 2 +- .../JNI/JNIClosureTests.swift | 129 +++++++++++++ 11 files changed, 556 insertions(+), 50 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift new file mode 100644 index 00000000..00d1f4b0 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public func emptyClosure(closure: () -> ()) { + closure() +} + +public func closureWithInt(input: Int64, closure: (Int64) -> Int64) -> Int64 { + return closure(input) +} + +public func closureMultipleArguments( + input1: Int64, + input2: Int64, + closure: (Int64, Int64) -> Int64 +) -> Int64 { + return closure(input1, input2) +} + + diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java new file mode 100644 index 00000000..b8389d41 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.*; + +public class ClosuresTest { + @Test + void emptyClosure() { + AtomicBoolean closureCalled = new AtomicBoolean(false); + MySwiftLibrary.emptyClosure(() -> { + closureCalled.set(true); + }); + assertTrue(closureCalled.get()); + } + + @Test + void closureWithInt() { + long result = MySwiftLibrary.closureWithInt(10, (value) -> value * 2); + assertEquals(20, result); + } + + @Test + void closureMultipleArguments() { + long result = MySwiftLibrary.closureMultipleArguments(5, 10, (a, b) -> a + b); + assertEquals(15, result); + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 9da3ae5b..f9a67419 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -17,21 +17,22 @@ import JavaTypes extension JavaType { var jniTypeSignature: String { switch self { - case .boolean: "Z" - case .byte: "B" - case .char: "C" - case .short: "S" - case .int: "I" - case .long: "J" - case .float: "F" - case .double: "D" + case .boolean: return "Z" + case .byte: return "B" + case .char: return "C" + case .short: return "S" + case .int: return "I" + case .long: return "J" + case .float: return "F" + case .double: return "D" case .class(let package, let name): + let nameWithInnerClasses = name.replacingOccurrences(of: ".", with: "$") if let package { - "L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);" + return "L\(package.replacingOccurrences(of: ".", with: "/"))/\(nameWithInnerClasses);" } else { - "L\(name);" + return "L\(nameWithInnerClasses);" } - case .array(let javaType): "[\(javaType.jniTypeSignature)" + case .array(let javaType): return "[\(javaType.jniTypeSignature)" case .void: fatalError("There is no type signature for 'void'") } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 9c3d14b8..e0e5cb70 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -76,12 +76,12 @@ extension JNISwift2JavaGenerator { for decl in analysis.importedGlobalFuncs { self.logger.trace("Print global function: \(decl)") - printFunctionBinding(&printer, decl) + printFunctionDowncallMethods(&printer, decl) printer.println() } for decl in analysis.importedGlobalVariables { - printFunctionBinding(&printer, decl) + printFunctionDowncallMethods(&printer, decl) printer.println() } } @@ -117,17 +117,17 @@ extension JNISwift2JavaGenerator { printer.println() for initializer in decl.initializers { - printFunctionBinding(&printer, initializer) + printFunctionDowncallMethods(&printer, initializer) printer.println() } for method in decl.methods { - printFunctionBinding(&printer, method) + printFunctionDowncallMethods(&printer, method) printer.println() } for variable in decl.variables { - printFunctionBinding(&printer, variable) + printFunctionDowncallMethods(&printer, variable) printer.println() } @@ -175,12 +175,67 @@ extension JNISwift2JavaGenerator { } } - private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let translatedDecl = translatedDecl(for: decl) else { + private func printFunctionDowncallMethods( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. return } + printer.printSeparator(decl.displayName) + + printJavaBindingWrapperHelperClass(&printer, decl) + + printJavaBindingWrapperMethod(&printer, decl) + } + + /// Print the helper type container for a user-facing Java API. + /// + /// * User-facing functional interfaces. + private func printJavaBindingWrapperHelperClass( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translated = self.translatedDecl(for: decl)! + if translated.functionTypes.isEmpty { + return + } + + printer.printBraceBlock( + """ + public static class \(translated.name) + """ + ) { printer in + for functionType in translated.functionTypes { + printJavaBindingWrapperFunctionTypeHelper(&printer, functionType) + } + } + } + + /// Print "wrapper" functional interface representing a Swift closure type. + func printJavaBindingWrapperFunctionTypeHelper( + _ printer: inout CodePrinter, + _ functionType: TranslatedFunctionType + ) { + let apiParams = functionType.parameters.map(\.parameter.asParameter) + + printer.print( + """ + @FunctionalInterface + public interface \(functionType.name) { + \(functionType.result.javaType) apply(\(apiParams.joined(separator: ", "))); + } + """ + ) + } + + private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + guard let translatedDecl = translatedDecl(for: decl) else { + fatalError("Decl was not translated, \(decl)") + } + var modifiers = ["public"] if decl.isStatic || decl.isInitializer || !decl.hasParent { modifiers.append("static") @@ -215,7 +270,7 @@ extension JNISwift2JavaGenerator { if let selfParameter = nativeSignature.selfParameter { parameters.append(selfParameter) } - let renderedParameters = parameters.map { "\($0.javaParameter.type) \($0.javaParameter.name)"}.joined(separator: ", ") + let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ") printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));") } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index e0253624..e704739c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(swiftModuleName: swiftModuleName) + let translation = JavaTranslation(swiftModuleName: swiftModuleName, javaPackage: self.javaPackage) translated = try translation.translate(decl) } catch { self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -37,17 +37,10 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { let swiftModuleName: String + let javaPackage: String func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { - let nativeTranslation = NativeJavaTranslation() - - // Swift -> Java - let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature) - // Java -> Java (native) - let nativeFunctionSignature = try nativeTranslation.translate( - functionSignature: decl.functionSignature, - translatedFunctionSignature: translatedFunctionSignature - ) + let nativeTranslation = NativeJavaTranslation(javaPackage: self.javaPackage) // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -59,19 +52,81 @@ extension JNISwift2JavaGenerator { case .function, .initializer: decl.name } + // Swift -> Java + let translatedFunctionSignature = try translate( + functionSignature: decl.functionSignature, + methodName: javaName, + parentName: parentName + ) + // Java -> Java (native) + let nativeFunctionSignature = try nativeTranslation.translate( + functionSignature: decl.functionSignature, + translatedFunctionSignature: translatedFunctionSignature, + methodName: javaName, + parentName: parentName + ) + + // Closures. + var funcTypes: [TranslatedFunctionType] = [] + for (idx, param) in decl.functionSignature.parameters.enumerated() { + let parameterName = param.parameterName ?? "_\(idx)" + + switch param.type { + case .function(let funcTy): + let translatedClosure = try translateFunctionType( + name: parameterName, + swiftType: funcTy, + parentName: parentName + ) + funcTypes.append(translatedClosure) + default: + break + } + } + return TranslatedFunctionDecl( name: javaName, nativeFunctionName: "$\(javaName)", parentName: parentName, + functionTypes: funcTypes, translatedFunctionSignature: translatedFunctionSignature, nativeFunctionSignature: nativeFunctionSignature ) } - func translate(functionSignature: SwiftFunctionSignature) throws -> TranslatedFunctionSignature { + /// Translate Swift closure type to Java functional interface. + func translateFunctionType( + name: String, + swiftType: SwiftFunctionType, + parentName: String + ) throws -> TranslatedFunctionType { + var translatedParams: [TranslatedParameter] = [] + + for (i, param) in swiftType.parameters.enumerated() { + let paramName = param.parameterName ?? "_\(i)" + translatedParams.append( + try translateParameter(swiftType: param.type, parameterName: paramName, methodName: name, parentName: parentName) + ) + } + + let transltedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) + + return TranslatedFunctionType( + name: name, + parameters: translatedParams, + result: transltedResult, + swiftType: swiftType + ) + } + + func translate( + functionSignature: SwiftFunctionSignature, + methodName: String, + parentName: String + ) throws -> TranslatedFunctionSignature { let parameters = try functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" - return try translateParameter(swiftType: param.type, parameterName: parameterName) + return try translateParameter(swiftType: param.type, parameterName: parameterName, methodName: methodName, parentName: parentName) } // 'self' @@ -79,7 +134,9 @@ extension JNISwift2JavaGenerator { if case .instance(let swiftSelf) = functionSignature.selfParameter { selfParameter = try self.translateParameter( swiftType: swiftSelf.type, - parameterName: swiftSelf.parameterName ?? "self" + parameterName: swiftSelf.parameterName ?? "self", + methodName: methodName, + parentName: parentName ) } else { selfParameter = nil @@ -92,7 +149,12 @@ extension JNISwift2JavaGenerator { ) } - func translateParameter(swiftType: SwiftType, parameterName: String) throws -> TranslatedParameter { + func translateParameter( + swiftType: SwiftType, + parameterName: String, + methodName: String, + parentName: String + ) throws -> TranslatedParameter { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { @@ -121,7 +183,16 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .function: + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)") + ), + conversion: .placeholder + ) + + case .metatype, .optional, .tuple, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -168,6 +239,9 @@ extension JNISwift2JavaGenerator { /// The name of the Java parent scope this function is declared in let parentName: String + /// Functional interfaces required for the Java method. + let functionTypes: [TranslatedFunctionType] + /// Function signature of the Java function the user will call let translatedFunctionSignature: TranslatedFunctionSignature @@ -220,6 +294,16 @@ extension JNISwift2JavaGenerator { let conversion: JavaNativeConversionStep } + /// 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 + var swiftType: SwiftFunctionType + } + /// Describes how to convert values between Java types and the native Java function enum JavaNativeConversionStep { /// The value being converted diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 461c7301..cae6010d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -17,14 +17,25 @@ import JavaTypes extension JNISwift2JavaGenerator { struct NativeJavaTranslation { + let javaPackage: String + /// Translates a Swift function into the native JNI method signature. func translate( functionSignature: SwiftFunctionSignature, - translatedFunctionSignature: TranslatedFunctionSignature + translatedFunctionSignature: TranslatedFunctionSignature, + methodName: String, + parentName: String ) throws -> NativeFunctionSignature { - let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { translatedParameter, swiftParameter in + let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { + translatedParameter, + swiftParameter in let parameterName = translatedParameter.parameter.name - return try translate(swiftParameter: swiftParameter, parameterName: parameterName) + return try translate( + swiftParameter: swiftParameter, + parameterName: parameterName, + methodName: methodName, + parentName: parentName + ) } // Lower the self parameter. @@ -32,7 +43,9 @@ extension JNISwift2JavaGenerator { case .instance(let selfParameter): try translate( swiftParameter: selfParameter, - parameterName: selfParameter.parameterName ?? "self" + parameterName: selfParameter.parameterName ?? "self", + methodName: methodName, + parentName: parentName ) case nil, .initializer(_), .staticMethod(_): nil @@ -47,7 +60,9 @@ extension JNISwift2JavaGenerator { func translate( swiftParameter: SwiftParameter, - parameterName: String + parameterName: String, + methodName: String, + parentName: String, ) throws -> NativeParameter { switch swiftParameter.type { case .nominal(let nominalType): @@ -57,28 +72,111 @@ extension JNISwift2JavaGenerator { } return NativeParameter( - javaParameter: JavaParameter(name: parameterName, type: javaType), + name: parameterName, + javaType: javaType, conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) ) } case .tuple([]): return NativeParameter( - javaParameter: JavaParameter(name: parameterName, type: .void), + name: parameterName, + javaType: .void, conversion: .placeholder ) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .function(let fn): + var parameters = [NativeParameter]() + for (i, parameter) in fn.parameters.enumerated() { + let parameterName = parameter.parameterName ?? "_\(i)" + let closureParameter = try translateClosureParameter( + parameter.type, + parameterName: parameterName + ) + parameters.append(closureParameter) + } + + let result = try translateClosureResult(fn.resultType) + + return NativeParameter( + name: parameterName, + javaType: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + conversion: .closureLowering( + parameters: parameters, + result: result + ) + ) + + case .metatype, .optional, .tuple, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } // Classes are passed as the pointer. return NativeParameter( - javaParameter: JavaParameter(name: parameterName, type: .long), + name: parameterName, + javaType: .long, conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) ) } + func translateClosureResult( + _ type: SwiftType + ) throws -> NativeResult { + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + + // Only support primitives for now. + return NativeResult( + javaType: javaType, + conversion: .initFromJNI(.placeholder, swiftType: type) + ) + } + + // Custom types are not supported yet. + throw JavaTranslationError.unsupportedSwiftType(type) + + case .tuple([]): + return NativeResult( + javaType: .void, + conversion: .placeholder + ) + + case .function, .metatype, .optional, .tuple, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + + func translateClosureParameter( + _ type: SwiftType, + parameterName: String + ) throws -> NativeParameter { + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + + // Only support primitives for now. + return NativeParameter( + name: parameterName, + javaType: javaType, + conversion: .getJValue(.placeholder) + ) + } + + // Custom types are not supported yet. + throw JavaTranslationError.unsupportedSwiftType(type) + + case .function, .metatype, .optional, .tuple, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + func translate( swiftResult: SwiftResult ) throws -> NativeResult { @@ -122,10 +220,11 @@ extension JNISwift2JavaGenerator { } struct NativeParameter { - let javaParameter: JavaParameter + let name: String + let javaType: JavaType var jniType: JNIType { - javaParameter.type.jniType + javaType.jniType } /// Represents how to convert the JNI parameter to a Swift parameter @@ -145,6 +244,9 @@ extension JNISwift2JavaGenerator { /// `value.getJNIValue(in:)` indirect case getJNIValue(NativeSwiftConversionStep) + /// `value.getJValue(in:)` + indirect case getJValue(NativeSwiftConversionStep) + /// `SwiftType(from: value, in: environment!)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) @@ -152,12 +254,13 @@ extension JNISwift2JavaGenerator { indirect case extractSwiftValue(NativeSwiftConversionStep, swiftType: SwiftType) /// Allocate memory for a Swift value and outputs the pointer - indirect case allocateSwiftValue(name: String, swiftType: SwiftType) + case allocateSwiftValue(name: String, 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(NativeSwiftConversionStep) + indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -171,6 +274,10 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(inner).getJNIValue(in: environment!)" + case .getJValue(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).getJValue(in: environment!)" + case .initFromJNI(let inner, let swiftType): let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment!)" @@ -203,6 +310,44 @@ extension JNISwift2JavaGenerator { case .pointee(let inner): let inner = inner.render(&printer, placeholder) return "\(inner).pointee" + + case .closureLowering(let parameters, let nativeResult): + var printer = CodePrinter() + + let methodSignature = MethodSignature( + resultType: nativeResult.javaType, + parameterTypes: parameters.map(\.javaType) + ) + + let closureParameters = !parameters.isEmpty ? "\(parameters.map(\.name).joined(separator: ", ")) in" : "" + printer.print("{ \(closureParameters)") + printer.indent() + + let arguments = parameters.map { + $0.conversion.render(&printer, $0.name) + } + + printer.print( + """ + let class$ = environment!.interface.GetObjectClass(environment, \(placeholder)) + let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! + let arguments$: [jvalue] = [\(arguments.joined(separator: ", "))] + """ + ) + + let upcall = "environment!.interface.\(nativeResult.javaType.jniType.callMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let result = nativeResult.conversion.render(&printer, upcall) + + if nativeResult.javaType.isVoid { + printer.print(result) + } else { + printer.print("return \(result)") + } + + printer.outdent() + printer.print("}") + + return printer.finalize() } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index d8b6b275..20551465 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -135,7 +135,7 @@ extension JNISwift2JavaGenerator { &printer, javaMethodName: translatedDecl.nativeFunctionName, parentName: translatedDecl.parentName, - parameters: parameters.map(\.javaParameter), + parameters: parameters.map { JavaParameter(name: $0.name, type: $0.javaType) }, resultType: nativeSignature.result.javaType.jniType ) { printer in self.printFunctionDowncall(&printer, decl) @@ -156,7 +156,7 @@ extension JNISwift2JavaGenerator { // Regular parameters. var arguments = [String]() for parameter in nativeSignature.parameters { - let lowered = parameter.conversion.render(&printer, parameter.javaParameter.name) + let lowered = parameter.conversion.render(&printer, parameter.name) arguments.append(lowered) } @@ -273,6 +273,7 @@ extension JNISwift2JavaGenerator { // Generated by swift-java import JavaKit + import JavaRuntime """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift index d152942a..feb8a545 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIType.swift @@ -40,6 +40,22 @@ enum JNIType { case jfloatArray case jdoubleArray case jobjectArray + + var callMethodAName: String { + switch self { + case .jboolean: "CallBooleanMethodA" + case .jbyte: "CallByteMethodA" + case .jchar: "CallCharMethodA" + case .jshort: "CallShortMethodA" + case .jint: "CallIntMethodA" + case .jlong: "CallLongMethodA" + case .jfloat: "CallFloatMethodA" + case .jdouble: "CallDoubleMethodA" + case .void: "CallVoidMethodA" + case .jobject, .jstring, .jclass, .jthrowable: "CallObjectMethodA" + case .jbooleanArray, .jbyteArray, .jcharArray, .jshortArray, .jintArray, .jlongArray, .jfloatArray, .jdoubleArray, .jobjectArray: "CallObjectMethodA" + } + } } extension JavaType { diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/JavaKit/JavaEnvironment.swift index d74146ab..4895b32c 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/JavaKit/JavaEnvironment.swift @@ -15,5 +15,5 @@ import JavaRuntime extension UnsafeMutablePointer { - var interface: JNINativeInterface_ { self.pointee!.pointee } + public var interface: JNINativeInterface_ { self.pointee!.pointee } } diff --git a/Sources/JavaKit/JavaValue.swift b/Sources/JavaKit/JavaValue.swift index 1bc156a0..0f208144 100644 --- a/Sources/JavaKit/JavaValue.swift +++ b/Sources/JavaKit/JavaValue.swift @@ -137,7 +137,7 @@ extension JavaValue where Self: ~Copyable { } /// Convert to a jvalue within the given JNI environment. - consuming func getJValue(in environment: JNIEnvironment) -> jvalue { + public consuming func getJValue(in environment: JNIEnvironment) -> jvalue { var result = jvalue() Self.assignJNIType(&result[keyPath: Self.jvalueKeyPath], to: getJNIValue(in: environment)) return result diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift new file mode 100644 index 00000000..47d7e35d --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIClosureTests { + let source = + """ + public func emptyClosure(closure: () -> ()) {} + public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) {} + """ + + @Test + func emptyClosure_javaBindings() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + public static class emptyClosure { + @FunctionalInterface + public interface closure { + void apply(); + } + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func emptyClosure(closure: () -> ()) + * } + */ + public static void emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure) { + SwiftModule.$emptyClosure(closure); + } + """, + """ + private static native void $emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure); + """ + ]) + } + + @Test + func emptyClosure_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") + func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + SwiftModule.emptyClosure(closure: { + let class$ = environment!.interface.GetObjectClass(environment, closure) + let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! + let arguments$: [jvalue] = [] + environment!.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + } + ) + } + """ + ] + ) + } + + @Test + func closureWithArgumentsAndReturn_javaBindings() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + public static class closureWithArgumentsAndReturn { + @FunctionalInterface + public interface closure { + long apply(long _0, boolean _1); + } + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) + * } + */ + public static void closureWithArgumentsAndReturn(com.example.swift.SwiftModule.closureWithArgumentsAndReturn.closure closure) { + SwiftModule.$closureWithArgumentsAndReturn(closure); + } + """, + """ + private static native void $closureWithArgumentsAndReturn(com.example.swift.SwiftModule.closureWithArgumentsAndReturn.closure closure); + """ + ]) + } + + @Test + func closureWithArgumentsAndReturn_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") + func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in + let class$ = environment!.interface.GetObjectClass(environment, closure) + let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! + let arguments$: [jvalue] = [_0.getJValue(in: environment!), _1.getJValue(in: environment!)] + return Int64(fromJNI: environment!.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment!) + } + ) + } + """ + ] + ) + } +} From 342b5ea87a04666adf8b7e1a4ec10741c3ae64ee Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 23 Jul 2025 20:26:54 +0900 Subject: [PATCH 107/178] docc documentation, first step (#319) --- .github/actions/prepare_env/action.yml | 6 +- .github/scripts/install_swiftly.sh | 6 +- .github/scripts/validate_docs.sh | 13 + .github/workflows/pull_request.yml | 25 ++ .gitignore | 1 + .spi.yml | 8 + Package.swift | 14 + .../JavaKit/Documentation.docc/JavaKit.md | 254 +---------------- .../Documentation.docc/SupportedFeatures.md | 94 +++++++ .../SwiftJavaCommandLineTool.md | 256 ++++++++++++++++++ .../Documentation.docc/SwiftPMPlugin.md | 126 +++++++++ .../Documentation.docc/index.md | 32 +++ Sources/SwiftJavaDocumentation/empty.swift | 15 + WIP.md | 9 - 14 files changed, 595 insertions(+), 264 deletions(-) create mode 100755 .github/scripts/validate_docs.sh create mode 100644 .spi.yml rename USER_GUIDE.md => Sources/JavaKit/Documentation.docc/JavaKit.md (62%) create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/index.md create mode 100644 Sources/SwiftJavaDocumentation/empty.swift diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 8fc986e0..53bb3fd7 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -26,9 +26,9 @@ runs: elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV fi - - name: Check Java environment - shell: bash - run: ./gradlew -q javaToolchains + # - name: Check Java environment + # shell: bash + # run: ./gradlew -q javaToolchains - name: Cache local SwiftPM repository if: matrix.os_version == 'jammy' uses: actions/cache@v4 diff --git a/.github/scripts/install_swiftly.sh b/.github/scripts/install_swiftly.sh index 78fa3f6b..e0792894 100755 --- a/.github/scripts/install_swiftly.sh +++ b/.github/scripts/install_swiftly.sh @@ -41,6 +41,7 @@ else curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && pkgutil --check-signature swiftly.pkg && pkgutil --verbose --expand swiftly.pkg "${SWIFTLY_HOME_DIR}" && tar -C "${SWIFTLY_HOME_DIR}" -xvf "${SWIFTLY_HOME_DIR}"/swiftly-*/Payload && "$SWIFTLY_HOME_DIR/bin/swiftly" init -y --skip-install + chmod +x "$SWIFTLY_HOME_DIR/env.sh" # shellcheck disable=SC1091 . "$SWIFTLY_HOME_DIR/env.sh" fi @@ -84,5 +85,8 @@ echo "Displaying swift version" swiftly run "${runSelector[@]}" swift --version if [[ "$(uname -s)" == "Linux" ]]; then - CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh" + if [[ -f "$(dirname "$0")/install-libarchive.sh" ]]; then + CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh" + fi fi + diff --git a/.github/scripts/validate_docs.sh b/.github/scripts/validate_docs.sh new file mode 100755 index 00000000..8ee777b0 --- /dev/null +++ b/.github/scripts/validate_docs.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e +set -x + +cat <> Package.swift + +package.dependencies.append( + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0") +) +EOF + +swift package --disable-sandbox plugin generate-documentation --target "SwiftJavaDocumentation" --warnings-as-errors --analyze diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9a0b59b8..b2338ed2 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -13,6 +13,31 @@ jobs: # FIXME: Something is off with the format task and it gets "stuck", need to investigate format_check_enabled: false license_header_check_project_name: Swift.org + # Since we need JAVA_HOME to be set up for building the project and depending on a different workflow won't + # give us that, we disable the checking and make a separate job that performs docs validation + docs_check_enabled: false + + # This replicates 'docs-check' from https://github.com/swiftlang/github-workflows/blob/main/.github/workflows/soundness.yml + # because we need to set up environment so we can build the SwiftJava project (including Java runtime/dependencies). + soundness-docs: + name: Documentation check + runs-on: ubuntu-latest + container: + image: 'swift:6.1-noble' + strategy: + fail-fast: true + matrix: + swift_version: ['6.1.2'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Swift Build + run: swift build + - name: Run documentation check + run: ./.github/scripts/validate_docs.sh test-java: name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) diff --git a/.gitignore b/.gitignore index f77390a7..28dfbae2 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ Package.resolved */**/*.o */**/*.swiftdeps */**/*.swiftdeps~ +*/**/.docc-build/ diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 00000000..58345f25 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,8 @@ +version: 1 +builder: + configs: + - documentation_targets: [ + SwiftJavaDocumentation, + JavaKit, + SwiftKitSwift + ] diff --git a/Package.swift b/Package.swift index 81d53b1d..ef529496 100644 --- a/Package.swift +++ b/Package.swift @@ -139,6 +139,12 @@ let package = Package( name: "swift-java", targets: ["SwiftJavaTool"] ), + + + .library( + name: "SwiftJavaDocumentation", + targets: ["SwiftJavaDocumentation"] + ), // ==== Plugin for building Java code .plugin( @@ -198,6 +204,14 @@ let package = Package( .package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")), ], targets: [ + .target( + name: "SwiftJavaDocumentation", + dependencies: [ + "JavaKit", + "SwiftKitSwift", + ] + ), + .macro( name: "JavaKitMacros", dependencies: [ diff --git a/USER_GUIDE.md b/Sources/JavaKit/Documentation.docc/JavaKit.md similarity index 62% rename from USER_GUIDE.md rename to Sources/JavaKit/Documentation.docc/JavaKit.md index 351a3d5c..fe0f50ba 100644 --- a/USER_GUIDE.md +++ b/Sources/JavaKit/Documentation.docc/JavaKit.md @@ -2,11 +2,7 @@ Library and tools to make it easy to use Java libraries from Swift using the Java Native Interface (JNI). -## Getting started - -Before using this package, set the `JAVA_HOME` environment variable to point at your Java installation. Failing to do so will produce errors when processing the package manifest. Alternatively, you can put the path to your Java installation in the file `~/.java_home`. - -### Using Java libraries from Swift +## JavaKit: Using Java libraries from Swift 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`: @@ -282,7 +278,7 @@ Java native methods that throw any checked exception should be marked as `throws The Swift implementations of Java `native` constructors and static methods require an additional Swift parameter `environment: JNIEnvironment? = nil`, which will receive the JNI environment in which the function is being executed. In case of nil, the `JavaVirtualMachine.shared().environment()` value will be used. -## Using Java libraries from Swift +## JavaKit: Using Java libraries from Swift This section describes how Java libraries and mapped into Swift and their use from Swift. @@ -420,7 +416,7 @@ extension JavaClass { } ``` -### Interfaces +### Java Interfaces Java interfaces are similar to classes, and are projected into Swift in much the same way, but with the macro `JavaInterface`. The `JavaInterface` macro takes the Java interface name as well as any Java interfaces that this interface extends. As an example, here is the Swift projection of the [`java.util.Enumeration`](https://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) generic interface: @@ -437,247 +433,3 @@ public struct Enumeration { public func nextElement() -> JavaObject! } ``` - -## Translating Java classes with `swift-java` - -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-java -``` - -to produce help output like the following: - -``` -OVERVIEW: Generate sources and configuration for Swift and Java interoperability. - -USAGE: swift-java - -OPTIONS: - --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. - --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 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 swift-java --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config -``` - -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 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 `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`: - -``` -warning: Unable to translate 'java.util.jar.JarOutputStream' superclass: Java class 'java.util.zip.ZipOutputStream' has not been translated into Swift -warning: Unable to translate 'java.util.jar.JarOutputStream' constructor: Java class 'java.io.OutputStream' has not been translated into Swift -warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo': Java class 'java.io.OutputStream' has not been translated into Swift -``` - -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 `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 - -**NOTE**: the instructions here work, but we are still smoothing out the interoperability story. - -All JavaKit-based applications start execution within the Java Virtual Machine. First, define your own Java class that loads your native Swift library and provides a `native` entry point to get into the Swift code. Here is a minimal Java class that has all of the program's logic written in Swift, including `main`: - - -```java -package org.swift.javakit; - -public class HelloSwiftMain { - static { - System.loadLibrary("HelloSwift"); - } - - public native static void main(String[] args); -} -``` - -Compile this into a `.class` file with `javac` before we build the Swift half, e.g.,: - -``` -javac Java/src/org/swift/javakit/JavaClassTranslator.java -``` - -### Create a Swift library - -The Java class created above loads a native library `HelloSwift` that needs to contain a definition of the `main` method in the class `org.swift.javakit.HelloSwiftMain`. `HelloSwift` should be defined as a SwiftPM dynamic library product, e.g., - -```swift - products: [ - .library( - name: "HelloSwift", - type: .dynamic, - targets: ["HelloSwift"] - ), - ] -``` - -with an associated target that depends on `JavaKit`: - -```swift - .target( - name: "HelloSwift", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - .product(name: "JavaKit", package: "JavaKit") - ]) -``` - -### Implement the `native` Java method in Swift -Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined: - -```swift -import JavaKit - -@JavaImplementation("org.swift.javakit.HelloSwiftMain") -struct HelloSwiftMain { - @JavaStaticMethod - static func main(arguments: [String], environment: JNIEnvironment? = nil) { - print("Command line arguments are: \(arguments)") - } -} -``` - -Go ahead and build this library with `swift build`, and find the path to the directory containing the resulting shared library (e.g., `HelloSwift.dylib`, `HelloSwift.so`, or `HelloSwift.dll`, depending on platform). It is often in `.build/debug/` if you ran `swift build` on the command line. - -### Putting it all together! - -Finally, run this program on the command line like this: - -``` -java -cp Java/src -Djava.library.path=$(PATH_CONTAINING_HELLO_SWIFT)/ org.swift.javakit.HelloSwiftMain -v argument -``` - -This will prints the command-line arguments `-v` and `argument` as seen by Swift. - -### Bonus: Swift argument parser - -The easiest way to build a command-line program in Swift is with the [Swift argument parser library](https://github.com/apple/swift-argument-parser). We can extend our `HelloSwiftMain` type to conform to `ParsableCommand` and using the Swift argument parser to process the arguments provided by Java: - -```swift -import ArgumentParser -import JavaKit - -@JavaClass("org.swift.javakit.HelloSwiftMain") -struct HelloSwiftMain: ParsableCommand { - @Option(name: .shortAndLong, help: "Enable verbose output") - var verbose: Bool = false - - @JavaImplementation - static func main(arguments: [String], environment: JNIEnvironment? = nil) { - let command = Self.parseOrExit(arguments) - command.run(environment: environment) - } - - func run(environment: JNIEnvironment? = nil) { - print("Verbose = \(verbose)") - } -} -``` - -# `jextract-swift` - -The project is still very early days, however the general outline of using this approach is as follows: - -- **No code changes** need to be made to Swift libraries that are to be exposed to Java using jextract-swift. -- Swift sources are compiled to `.swiftinterface` files -- These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files -- The generated Java files contain generated code for efficient native invocations. - -You can then use Swift libraries in Java just by calling the appropriate methods and initializers. - -## `jextract-swift`: Generating Java bridging files - -This repository also includes the `jextract-swift` tool which is similar to the JDK's [`jextract`](https://github.com/openjdk/jextract/). - -This approach is using Java's most recent (stable in JDK22) Foreign function and Memory APIs, collectively known as "Project Panama". You can read more about it here: https://openjdk.org/projects/panama/ It promises much higher performance than traditional approaches using JNI, and is primarily aimed for calling native code from a Java application. - -:warning: This feature requires JDK 22. The recommended way to install/manage JDKs is using [sdkman](https://sdkman.io): - -``` -curl -s "https://get.sdkman.io" | bash -sdk install java 22-open - -export JAVA_HOME=$(sdk home java 22-open) -``` - -`jextract-swift` can be pointed at `*.swiftinterface` files and will generate corresponding Java files that use the (new in Java 22) Foreign Function & Memory APIs to expose efficient ways to call "down" into Swift from Java. - -## JExtract: Swift <-> Java Type mapping - -TODO: these are not implemented yet. - -### Closures and Callbacks - -A Swift function may accept a closure which is used as a callback: - -```swift -func callMe(maybe: () -> ()) {} -``` - -Minimal support for c-compatible closures is implemented, more documentation soon. - - -## `jextract-swift` importer behavior - -Only `public` functions, properties and types are imported. - -Global Swift functions become static functions on on a class with the same name as the Swift module in Java, - -```swift -// Swift (Sources/SomeModule/Example.swift) - -public func globalFunction() -``` - -becomes: - -```java -// Java (SomeModule.java) - -public final class SomeModule ... { - public static void globalFunction() { ... } -} -``` diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md new file mode 100644 index 00000000..dc2c7b0e --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -0,0 +1,94 @@ +# Supported Features + +Summary of features supported by the swift-java interoperability libraries and tools. + +## Overview + +JavaKit supports both directions of interoperability, using Swift macros and source generation +(via the `swift-java wrap-java` command). + +### Java -> Swift + +It is possible to use JavaKit macros and the `wrap-java` command to simplify implementing +Java `native` functions. JavaKit simplifies the type conversions + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [7-minute mark](https://youtu.be/QSHO-GUGidA?si=vUXxphTeO-CHVZ3L&t=448). + +| Feature | Macro support | +|--------------------------------------------------|-------------------------| +| Java `static native` method implemented by Swift | ✅ `@JavaImplementation` | +| **This list is very work in progress** | | + +### Swift -> Java + + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [10-minute mark](https://youtu.be/QSHO-GUGidA?si=QyYP5-p2FL_BH7aD&t=616). + +| Java Feature | Macro support | +|----------------------------------------|---------------| +| Java `class` | ✅ | +| Java class inheritance | ✅ | +| Java `abstract class` | TODO | +| Java `enum` | ❌ | +| Java methods: `static`, member | ✅ `@JavaMethod` | +| **This list is very work in progress** | | + + +### JExtract: Java -> Swift + +SwiftJava's `swift-java jextract` tool automates generating Java bindings from Swift sources. + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [14-minute mark](https://youtu.be/QSHO-GUGidA?si=b9YUwAWDWFGzhRXN&t=842). + + +| Swift Feature | FFM | JNI | +|--------------------------------------------------------------------------------------| -------- |-----| +| Initializers: `class`, `struct` | ✅ | ✅ | +| Optional Initializers / Throwing Initializers | ❌ | ❌ | +| Deinitializers: `class`, `struct` | ✅ | ✅ | +| `enum`, `actor` | ❌ | ❌ | +| Global Swift `func` | ✅ | ✅ | +| Class/struct member `func` | ✅ | ✅ | +| Throwing functions: `func x() throws` | ❌ | ✅ | +| Typed throws: `func x() throws(E)` | ❌ | ❌ | +| Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | +| Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | +| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | +| Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | +| Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | +| Generic functions | ❌ | ❌ | +| `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | +| Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | +| Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | +| Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | +| Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | +| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | +| String (with copying data) | ✅ | ✅ | +| Variadic parameters: `T...` | ❌ | ❌ | +| Parametrer packs / Variadic generics | ❌ | ❌ | +| Ownership modifiers: `inout`, `borrowing`, `consuming` | ❌ | ❌ | +| Default parameter values: `func p(name: String = "")` | ❌ | ❌ | +| Operators: `+`, `-`, user defined | ❌ | ❌ | +| Subscripts: `subscript()` | ❌ | ❌ | +| Equatable | ❌ | ❌ | +| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | +| Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | +| Inheritance: `class Caplin: Capybara` | ❌ | ❌ | +| Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | +| Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | +| Non-escaping closures with object arguments/results: `func callMe(maybe: (JavaObj) -> (JavaObj))` | ❌ | ❌ | +| `@escaping` closures: `func callMe(_: @escaping () -> ())` | ❌ | ❌ | +| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | +| Swift macros (maybe) | ❌ | ❌ | +| Result builders | ❌ | ❌ | +| Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | +| Value semantic types (e.g. struct copying) | ❌ | ❌ | +| Opaque types: `func get() -> some Builder`, func take(worker: some Worker) | ❌ | ❌ | +| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | +| | | | +| | | | + +> tip: The list of features may be incomplete, please file an issue if something is unclear or should be clarified in this table. diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md new file mode 100644 index 00000000..f83458d4 --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -0,0 +1,256 @@ +# swift-java command line tool + +The `swift-java` command line tool offers multiple ways to interact your Java interoperability enabled projects. + +## Overview + +The `swift-java` command line tool offers multiple modes which you can use to prepare your Swift and Java code to interact with eachother. + +The following sections will explain the modes in depth. When in doubt, you can always use the command line `--help` to get additional +guidance about the tool and available options: + +```bash +> swift-java --help + +USAGE: swift-java + +OPTIONS: + -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 + wrap-java Wrap Java classes with corresponding Swift bindings. + jextract Wrap Swift functions and types with Java bindings, making them available to be called from Java + + See 'swift-java help ' for detailed help. +``` + +### Expose Java classes to Swift: swift-java wrap-java + +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-java help wrap-java +``` + +to produce help output like the following: + +``` +USAGE: swift-java wrap-java [--output-directory ] [--input-swift ] [--log-level ] [--cp ...] [--filter-java-package ] --swift-module [--depends-on ...] [--swift-native-implementation ...] [--cache-directory ] [--swift-match-package-directory-structure ] + +ARGUMENTS: + Path to .jar file whose Java classes should be wrapped using Swift bindings + +OPTIONS: + -o, --output-directory + The directory in which to output generated SwiftJava configuration files. + --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 + --swift-module + The name of the Swift module into which the resulting Swift types will be generated. + --depends-on + 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. + --swift-native-implementation + The names of Java classes whose declared native methods will be implemented in Swift. + --cache-directory + Cache directory for intermediate results and other outputs between runs + --swift-match-package-directory-structure + Match java package directory structure with generated Swift files (default: false) + -h, --help Show help information. + +``` + +For example, the `JavaKitJar` library is generated with this command line: + +```swift +swift-java wrap-java --swift-module JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config +``` + +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 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 `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`: + +``` +warning: Unable to translate 'java.util.jar.JarOutputStream' superclass: Java class 'java.util.zip.ZipOutputStream' has not been translated into Swift +warning: Unable to translate 'java.util.jar.JarOutputStream' constructor: Java class 'java.io.OutputStream' has not been translated into Swift +warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo': Java class 'java.io.OutputStream' has not been translated into Swift +``` + +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 `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 + +**NOTE**: the instructions here work, but we are still smoothing out the interoperability story. + +All JavaKit-based applications start execution within the Java Virtual Machine. First, define your own Java class that loads your native Swift library and provides a `native` entry point to get into the Swift code. Here is a minimal Java class that has all of the program's logic written in Swift, including `main`: + + +```java +package org.swift.javakit; + +public class HelloSwiftMain { + static { + System.loadLibrary("HelloSwift"); + } + + public native static void main(String[] args); +} +``` + +Compile this into a `.class` file with `javac` before we build the Swift half, e.g.,: + +``` +javac Java/src/org/swift/javakit/JavaClassTranslator.java +``` + +### Create a Swift library + +The Java class created above loads a native library `HelloSwift` that needs to contain a definition of the `main` method in the class `org.swift.javakit.HelloSwiftMain`. `HelloSwift` should be defined as a SwiftPM dynamic library product, e.g., + +```swift + products: [ + .library( + name: "HelloSwift", + type: .dynamic, + targets: ["HelloSwift"] + ), + ] +``` + +with an associated target that depends on `JavaKit`: + +```swift + .target( + name: "HelloSwift", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "JavaKit", package: "JavaKit") + ]) +``` + +### Implement the `native` Java method in Swift +Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined: + +```swift +import JavaKit + +@JavaImplementation("org.swift.javakit.HelloSwiftMain") +struct HelloSwiftMain { + @JavaStaticMethod + static func main(arguments: [String], environment: JNIEnvironment? = nil) { + print("Command line arguments are: \(arguments)") + } +} +``` + +Go ahead and build this library with `swift build`, and find the path to the directory containing the resulting shared library (e.g., `HelloSwift.dylib`, `HelloSwift.so`, or `HelloSwift.dll`, depending on platform). It is often in `.build/debug/` if you ran `swift build` on the command line. + +### Putting it all together! + +Finally, run this program on the command line like this: + +``` +java -cp Java/src -Djava.library.path=$(PATH_CONTAINING_HELLO_SWIFT)/ org.swift.javakit.HelloSwiftMain -v argument +``` + +This will prints the command-line arguments `-v` and `argument` as seen by Swift. + +### Bonus: Swift argument parser + +The easiest way to build a command-line program in Swift is with the [Swift argument parser library](https://github.com/apple/swift-argument-parser). We can extend our `HelloSwiftMain` type to conform to `ParsableCommand` and using the Swift argument parser to process the arguments provided by Java: + +```swift +import ArgumentParser +import JavaKit + +@JavaClass("org.swift.javakit.HelloSwiftMain") +struct HelloSwiftMain: ParsableCommand { + @Option(name: .shortAndLong, help: "Enable verbose output") + var verbose: Bool = false + + @JavaImplementation + static func main(arguments: [String], environment: JNIEnvironment? = nil) { + let command = Self.parseOrExit(arguments) + command.run(environment: environment) + } + + func run(environment: JNIEnvironment? = nil) { + print("Verbose = \(verbose)") + } +} +``` + +### Download Java dependencies in Swift builds: swift-java resolve + +> TIP: See the `Samples/DependencySampleApp` for a fully functional showcase of this mode. + +TODO: documentation on this feature + +### Expose Swift code to Java: swift-java jextract + +The project is still very early days, however the general outline of using this approach is as follows: + +- **No code changes** need to be made to Swift libraries that are to be exposed to Java using jextract-swift. +- Swift sources are compiled to `.swiftinterface` files +- These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files +- The generated Java files contain generated code for efficient native invocations. + +You can then use Swift libraries in Java just by calling the appropriate methods and initializers. + +### Generating Java bindings for Swift libraries + +This repository also includes the `jextract-swift` tool which is similar to the JDK's [`jextract`](https://github.com/openjdk/jextract/). + +This approach offers two modes of operation: + +- the default `--mode ffm` which uses the [JEP-424 Foreign function and Memory APIs](https://openjdk.org/jeps/424) which are available since JDK **22**. It promises much higher performance than traditional approaches using JNI, and is primarily aimed for calling native code from a Java application. + +> Tip: In order to use the ffm mode, you need to install a recent enough JDK (at least JDK 22). The recommended, and simplest way, to install the a JDK distribution of your choice is [sdkman](https://sdkman.io): +> +> ``` +> curl -s "https://get.sdkman.io" | bash +> sdk install java 22-open +> +> export JAVA_HOME=$(sdk home java 22-open) +> ``` + +`jextract-swift` can be pointed at `*.swiftinterface` files and will generate corresponding Java files that use the (new in Java 22) Foreign Function & Memory APIs to expose efficient ways to call "down" into Swift from Java. + +### Default jextract behaviors + +Only `public` functions, properties and types are imported. + +Global Swift functions become static functions on on a class with the same name as the Swift module in Java, + +```swift +// Swift (Sources/SomeModule/Example.swift) + +public func globalFunction() +``` + +becomes: + +```java +// Java (SomeModule.java) + +public final class SomeModule ... { + public static void globalFunction() { ... } +} +``` diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md new file mode 100644 index 00000000..d10ab387 --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md @@ -0,0 +1,126 @@ +# SwiftJava SwiftPM Plugin + +The `SwiftJavaPlugin` automates `swift-java` command line tool invocations during the build process. + +## Overview + +### Installing the plugin + +To install the SwiftPM plugin in your target of choice include the `swift-java` package dependency: + +```swift +import Foundation + +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#elseif os(Windows) + let javaPlatformIncludePath = "\(javaIncludePath)/win32" +#endif + +let package = Package( + name: "MyProject", + + products: [ + .library( + name: "JavaKitExample", + type: .dynamic, + targets: ["JavaKitExample"] + ), + ], + + dependencies: [ + .package(url: "https://github.com/apple/swift-java", from: "..."), + ], + + targets: [ + .target( + name: "MyProject", + dependencies: [ + // ... + ], + swiftSettings: [ + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ], + plugins: [ + .plugin(name: "JavaCompilerPlugin", package: "swift-java"), + .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), + ] + ), + ] +) +``` + +```swift + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// 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 + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + 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 + } +} +``` \ No newline at end of file diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md new file mode 100644 index 00000000..7c695a71 --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md @@ -0,0 +1,32 @@ +# ``SwiftJavaDocumentation`` + +The SwiftJava project enables interoperability between Swift and Java. + +## Overview + +This project contains a number of support packages, java libraries, tools and plugins that provide a complete +Swift and Java interoperability story. + +Please refer to articles about the specific direction of interoperability you are interested in. + +### Getting started + +TODO: Some general intro + +If you prefer a video introduction, you may want to this +[Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA) +WWDC 2025 session, +which is a quick overview of all the features and approaches offered by SwiftJava. + +## Topics + +### Supported Features + +- + + +### Source generation + +- +- + diff --git a/Sources/SwiftJavaDocumentation/empty.swift b/Sources/SwiftJavaDocumentation/empty.swift new file mode 100644 index 00000000..9c28861c --- /dev/null +++ b/Sources/SwiftJavaDocumentation/empty.swift @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// Empty file, this target is a docs-only target. diff --git a/WIP.md b/WIP.md index e1f58612..88671233 100644 --- a/WIP.md +++ b/WIP.md @@ -6,9 +6,6 @@ Here is a long yet still very incomplete list of things we would like to do or improve: - Expressivity gaps: - - [x] Translate Java exceptions into Swift and vice versa - - [ ] Expose Swift types to Java - - [x] Figure out when to promote from the local to the global heap - [ ] Automatically turn get/set method pairs into Swift properties? - [ ] Implement a global registry that lets us find the Swift type corresponding to a canonical Java class name (e.g., `java.net.URL` -> `JavaKitNetwork.URL`) - [ ] Introduce overloads of `is` and `as` on the Swift projections so that conversion to any implemented interface or extended superclass returns non-optional. @@ -16,14 +13,8 @@ improve: - [ ] Recognize Java's enum classes and map them into Swift well - [ ] Translate Java constants into Swift constants - [ ] Support nested classes - - [x] Streamline the definition of "main" code in Swift - [ ] Figure out how to subclass a Java class from Swift - Tooling - - [ ] Extract documentation comments from Java and put them in the Swift projections - - [ ] [SwiftPM build plugin](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) to generate the Swift projections from Java as part of the build - - [x] Figure out how to launch the Java runtime from Swift code so we don't need to always start via `java` - - [x] Figure out how to unit-test this framework using Swift Testing - - [x] Add a "Jar mode" to `Java2Swift` that translates all classes in the given Jar file. - [ ] Generate Swift projections for more common Java types into JavaKit libraries to make it easier to get started - [ ] Teach `Java2Swift` when to create extensions of already-translated types that pick up any members that couldn't be translated because of missing types. See, for example, how `JavaKitReflection` adds extensions to `JavaClass` based on types like `Method` and `Parameter` - Performance: From c7adc8e063b7d2a7b24c435998b8ef2057ed4ff9 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 24 Jul 2025 02:07:29 +0900 Subject: [PATCH 108/178] Build: don't crash when building in SPI and JAVA_HOME is missing (#329) --- Package.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Package.swift b/Package.swift index ef529496..84b348c5 100644 --- a/Package.swift +++ b/Package.swift @@ -31,6 +31,11 @@ func findJavaHome() -> String { return home } + + if ProcessInfo.processInfo.environment["SPI_BUILD"] == "1" { + // just ignore that we're missing a JAVA_HOME when building in Swift Package Index + return "" + } fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") } From 11dcbedc315845756b7dbf1ff6fa5369dc0d9f08 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 24 Jul 2025 15:42:04 +0200 Subject: [PATCH 109/178] [JExtract/JNI] Support JavaKit wrapped types as function parameters. (#330) --- .../JExtractSwiftPlugin.swift | 64 +++++++++++++++- .../Sources/MySwiftLibrary/MySwiftClass.swift | 6 ++ .../com/example/swift/MySwiftClassTest.java | 9 +++ .../Convenience/String+Extensions.swift | 39 ++++++++++ ...ISwift2JavaGenerator+JavaTranslation.swift | 42 +++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 53 +++++++++---- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 26 ------- .../JNI/JNISwift2JavaGenerator.swift | 10 ++- Sources/JExtractSwiftLib/Swift2Java.swift | 15 +++- .../Swift2JavaTranslator.swift | 17 ++++- .../SwiftTypes/SwiftType.swift | 7 ++ .../Configuration.swift | 20 +++++ .../Documentation.docc/SupportedFeatures.md | 2 + .../Commands/JExtractCommand.swift | 20 ++++- .../Commands/WrapJavaCommand.swift | 28 ++----- Sources/SwiftJavaTool/SwiftJava.swift | 4 +- .../Asserts/TextAssertions.swift | 5 +- .../JNI/JNIJavaKitTests.swift | 74 +++++++++++++++++++ 18 files changed, 362 insertions(+), 79 deletions(-) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 83c5f0d9..2778cebe 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -15,6 +15,8 @@ import Foundation import PackagePlugin +fileprivate let SwiftJavaConfigFileName = "swift-java.config" + @main struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { @@ -51,12 +53,14 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("Skipping jextract step, no 'javaPackage' configuration in \(getSwiftJavaConfigPath(target: target) ?? "")") return [] } - + // We use the the usual maven-style structure of "src/[generated|main|test]/java/..." // that is common in JVM ecosystem let outputJavaDirectory = context.outputJavaDirectory let outputSwiftDirectory = context.outputSwiftDirectory + let dependentConfigFiles = searchForDependentConfigFiles(in: target) + var arguments: [String] = [ /*subcommand=*/"jextract", "--swift-module", sourceModule.name, @@ -71,6 +75,16 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // 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. ] + + let dependentConfigFilesArguments = dependentConfigFiles.flatMap { moduleAndConfigFile in + let (moduleName, configFile) = moduleAndConfigFile + return [ + "--depends-on", + "\(configFile.path(percentEncoded: false))" + ] + } + arguments += dependentConfigFilesArguments + if !javaPackage.isEmpty { arguments += ["--java-package", javaPackage] } @@ -117,5 +131,53 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { ) ] } + + /// Find the manifest files from other swift-java executions in any targets + /// this target depends on. + func searchForDependentConfigFiles(in target: any Target) -> [(String, URL)] { + var dependentConfigFiles = [(String, URL)]() + + func _searchForConfigFiles(in target: any Target) { + // log("Search for config files in target: \(target.name)") + let dependencyURL = URL(filePath: target.directory.string) + + // Look for a config file within this target. + let dependencyConfigURL = dependencyURL + .appending(path: SwiftJavaConfigFileName) + let dependencyConfigString = dependencyConfigURL + .path(percentEncoded: false) + + if FileManager.default.fileExists(atPath: dependencyConfigString) { + dependentConfigFiles.append((target.name, dependencyConfigURL)) + } + } + + // Process direct dependencies of this target. + for dependency in target.dependencies { + switch dependency { + case .target(let target): + // log("Dependency target: \(target.name)") + _searchForConfigFiles(in: target) + + case .product(let product): + // log("Dependency product: \(product.name)") + for target in product.targets { + // log("Dependency product: \(product.name), target: \(target.name)") + _searchForConfigFiles(in: target) + } + + @unknown default: + break + } + } + + // Process indirect target dependencies. + for dependency in target.recursiveTargetDependencies { + // log("Recursive dependency target: \(dependency.name)") + _searchForConfigFiles(in: dependency) + } + + return dependentConfigFiles + } } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index b4447f4f..9a78c3c2 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import JavaKit + public class MySwiftClass { public let x: Int64 public let y: Int64 @@ -84,4 +86,8 @@ public class MySwiftClass { public func copy() -> MySwiftClass { return MySwiftClass(x: self.x, y: self.y) } + + public func addXWithJavaLong(_ other: JavaLong) -> Int64 { + return self.x + other.longValue() + } } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 4425d6b2..f034b904 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -139,4 +139,13 @@ void copy() { assertNotEquals(c1.$memoryAddress(), c2.$memoryAddress()); } } + + @Test + void addXWithJavaLong() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + Long javaLong = 50L; + assertEquals(70, c1.addXWithJavaLong(javaLong)); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 1851e154..0f5cfeac 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import JavaTypes + extension String { var firstCharacterUppercased: String { @@ -31,4 +33,41 @@ extension String { let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) return self[thirdCharacterIndex].isUppercase } + + /// Returns a version of the string correctly escaped for a JNI + var escapedJNIIdentifier: String { + self.map { + if $0 == "_" { + return "_1" + } else if $0 == "/" { + return "_" + } else if $0 == ";" { + return "_2" + } else if $0 == "[" { + return "_3" + } else if $0.isASCII && ($0.isLetter || $0.isNumber) { + return String($0) + } else if let utf16 = $0.utf16.first { + // Escape any non-alphanumeric to their UTF16 hex encoding + let utf16Hex = String(format: "%04x", utf16) + return "_0\(utf16Hex)" + } else { + fatalError("Invalid JNI character: \($0)") + } + } + .joined() + } + + /// Looks up self as a JavaKit wrapped class name and converts it + /// into a `JavaType.class` if it exists in `lookupTable`. + func parseJavaClassFromJavaKitName(in lookupTable: [String: String]) -> JavaType? { + guard let canonicalJavaName = lookupTable[self] else { + return nil + } + let nameParts = canonicalJavaName.components(separatedBy: ".") + let javaPackageName = nameParts.dropLast().joined(separator: ".") + let javaClassName = nameParts.last! + + return .class(package: javaPackageName, name: javaClassName) + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index e704739c..57ea15b2 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,11 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(swiftModuleName: swiftModuleName, javaPackage: self.javaPackage) + let translation = JavaTranslation( + swiftModuleName: swiftModuleName, + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) translated = try translation.translate(decl) } catch { self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -38,9 +42,13 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { let swiftModuleName: String let javaPackage: String + let javaClassLookupTable: JavaClassLookupTable func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { - let nativeTranslation = NativeJavaTranslation(javaPackage: self.javaPackage) + let nativeTranslation = NativeJavaTranslation( + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -157,6 +165,8 @@ extension JNISwift2JavaGenerator { ) throws -> TranslatedParameter { switch swiftType { case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -168,11 +178,25 @@ extension JNISwift2JavaGenerator { ) } - // For now, we assume this is a JExtract class. + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: javaType + ), + conversion: .placeholder + ) + } + + // We assume this is a JExtract class. return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: nominalType.nominalTypeDecl.name) + type: .class(package: nil, name: nominalTypeName) ), conversion: .valueMemoryAddress(.placeholder) ) @@ -213,7 +237,11 @@ extension JNISwift2JavaGenerator { ) } - // For now, we assume this is a JExtract class. + if nominalType.isJavaKitWrapper { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + // We assume this is a JExtract class. let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) return TranslatedResult( javaType: javaType, @@ -350,5 +378,9 @@ extension JNISwift2JavaGenerator { enum JavaTranslationError: Error { case unsupportedSwiftType(SwiftType) + + /// The user has not supplied a mapping from `SwiftType` to + /// a java class. + case wrappedJavaClassTranslationNotProvided(SwiftType) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index cae6010d..5ccefadb 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -18,6 +18,7 @@ extension JNISwift2JavaGenerator { struct NativeJavaTranslation { let javaPackage: String + let javaClassLookupTable: JavaClassLookupTable /// Translates a Swift function into the native JNI method signature. func translate( @@ -62,10 +63,12 @@ extension JNISwift2JavaGenerator { swiftParameter: SwiftParameter, parameterName: String, methodName: String, - parentName: String, + parentName: String ) throws -> NativeParameter { switch swiftParameter.type { case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) @@ -78,6 +81,25 @@ extension JNISwift2JavaGenerator { ) } + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftParameter.type) + } + + return NativeParameter( + name: parameterName, + javaType: javaType, + conversion: .initializeJavaKitWrapper(wrapperName: nominalTypeName) + ) + } + + // JExtract classes are passed as the pointer. + return NativeParameter( + name: parameterName, + javaType: .long, + conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) + ) + case .tuple([]): return NativeParameter( name: parameterName, @@ -110,13 +132,6 @@ extension JNISwift2JavaGenerator { case .metatype, .optional, .tuple, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } - - // Classes are passed as the pointer. - return NativeParameter( - name: parameterName, - javaType: .long, - conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) - ) } func translateClosureResult( @@ -193,6 +208,15 @@ extension JNISwift2JavaGenerator { ) } + if nominalType.isJavaKitWrapper { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return NativeResult( + javaType: .long, + conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) + ) + case .tuple([]): return NativeResult( javaType: .void, @@ -203,13 +227,7 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - // TODO: Handle other classes, for example from JavaKit macros. - // for now we assume all passed in classes are JExtract generated - // so we pass the pointer. - return NativeResult( - javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) - ) + } } @@ -262,6 +280,8 @@ extension JNISwift2JavaGenerator { indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) + case initializeJavaKitWrapper(wrapperName: String) + /// 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. @@ -348,6 +368,9 @@ extension JNISwift2JavaGenerator { printer.print("}") return printer.finalize() + + case .initializeJavaKitWrapper(let wrapperName): + return "\(wrapperName)(javaThis: \(placeholder), environment: environment!)" } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 20551465..35d4dbef 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -328,29 +328,3 @@ extension JNISwift2JavaGenerator { return newSelfParamName } } - -extension String { - /// Returns a version of the string correctly escaped for a JNI - var escapedJNIIdentifier: String { - self.map { - if $0 == "_" { - return "_1" - } else if $0 == "/" { - return "_" - } else if $0 == ";" { - return "_2" - } else if $0 == "[" { - return "_3" - } else if $0.isASCII && ($0.isLetter || $0.isNumber) { - return String($0) - } else if let utf16 = $0.utf16.first { - // Escape any non-alphanumeric to their UTF16 hex encoding - let utf16Hex = String(format: "%04x", utf16) - return "_0\(utf16Hex)" - } else { - fatalError("Invalid JNI character: \($0)") - } - } - .joined() - } -} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 0cc523b4..79f546ac 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -14,6 +14,10 @@ import JavaTypes +/// A table that where keys are Swift class names and the values are +/// the fully qualified canoical names. +package typealias JavaClassLookupTable = [String: String] + package class JNISwift2JavaGenerator: Swift2JavaGenerator { let analysis: AnalysisResult let swiftModuleName: String @@ -22,6 +26,8 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { let swiftOutputDirectory: String let javaOutputDirectory: String + let javaClassLookupTable: JavaClassLookupTable + var javaPackagePath: String { javaPackage.replacingOccurrences(of: ".", with: "/") } @@ -39,7 +45,8 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, - javaOutputDirectory: String + javaOutputDirectory: String, + javaClassLookupTable: JavaClassLookupTable ) { self.logger = Logger(label: "jni-generator", logLevel: translator.log.logLevel) self.analysis = translator.result @@ -47,6 +54,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.javaPackage = javaPackage self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory + self.javaClassLookupTable = javaClassLookupTable // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index dad25299..6473cea3 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -20,9 +20,11 @@ import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigura public struct SwiftToJava { let config: Configuration + let dependentConfigs: [Configuration] - public init(config: Configuration) { + public init(config: Configuration, dependentConfigs: [Configuration]) { self.config = config + self.dependentConfigs = dependentConfigs } public func run() throws { @@ -83,6 +85,14 @@ public struct SwiftToJava { fatalError("Missing --output-java directory!") } + let wrappedJavaClassesLookupTable: JavaClassLookupTable = dependentConfigs.compactMap(\.classes).reduce(into: [:]) { + for (canonicalName, javaClass) in $1 { + $0[javaClass] = canonicalName + } + } + + translator.dependenciesClasses = Array(wrappedJavaClassesLookupTable.keys) + try translator.analyze() switch config.mode { @@ -101,7 +111,8 @@ public struct SwiftToJava { translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, - javaOutputDirectory: outputJavaDirectory + javaOutputDirectory: outputJavaDirectory, + javaClassLookupTable: wrappedJavaClassesLookupTable ) try generator.generate() diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 07023b09..d1141e5f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -39,6 +39,9 @@ public final class Swift2JavaTranslator { var inputs: [Input] = [] + /// A list of used Swift class names that live in dependencies, e.g. `JavaInteger` + package var dependenciesClasses: [String] = [] + // ==== Output state package var importedGlobalVariables: [ImportedFunc] = [] @@ -111,9 +114,11 @@ extension Swift2JavaTranslator { } package func prepareForTranslation() { + let dependenciesSource = self.buildDependencyClassesSourceFile() + self.symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, - inputs.map({ $0.syntax }), + inputs.map({ $0.syntax }) + [dependenciesSource], log: self.log ) } @@ -167,6 +172,16 @@ extension Swift2JavaTranslator { } return false } + + /// Returns a source file that contains all the available dependency classes. + private func buildDependencyClassesSourceFile() -> SourceFileSyntax { + let contents = self.dependenciesClasses.map { + "public class \($0) {}" + } + .joined(separator: "\n") + + return SourceFileSyntax(stringLiteral: contents) + } } // ==== ---------------------------------------------------------------------------------------------------------------- diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 531ab45c..d0f1f98d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -171,6 +171,13 @@ extension SwiftNominalType: CustomStringConvertible { } } +extension SwiftNominalType { + // TODO: Better way to detect Java wrapped classes. + var isJavaKitWrapper: Bool { + nominalTypeDecl.name.hasPrefix("Java") + } +} + extension SwiftType { init(_ type: TypeSyntax, symbolTable: SwiftSymbolTable) throws { switch type.as(TypeSyntaxEnum.self) { diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 876b577b..5c57082e 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -141,6 +141,26 @@ public func readConfiguration(configPath: URL, file: String = #fileID, line: UIn } } +/// Load all dependent configs configured with `--depends-on` and return a list of +/// `(SwiftModuleName, Configuration)` tuples. +public func loadDependentConfigs(dependsOn: [String]) throws -> [(String?, Configuration)] { + try dependsOn.map { dependentConfig in + let equalLoc = dependentConfig.firstIndex(of: "=") + + var swiftModuleName: String? = nil + if let equalLoc { + swiftModuleName = String(dependentConfig[.. [String] { let basePath: String = FileManager.default.currentDirectoryPath let pluginOutputsDir = URL(fileURLWithPath: basePath) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index dc2c7b0e..e7acb3eb 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -65,6 +65,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | | Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | +| Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | +| Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | | Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index c7fe51a8..e4901bc2 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -60,6 +60,15 @@ extension SwiftJava { @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 + + @Option( + help: """ + A swift-java configuration file for a given Swift module name on which this module depends, + e.g., 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] = [] } } @@ -82,15 +91,20 @@ extension SwiftJava.JExtractCommand { print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(self.mode)".bold) - try jextractSwift(config: config) + // Load all of the dependent configurations and associate them with Swift modules. + let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) + print("[debug][swift-java] Dependent configs: \(dependentConfigs.count)") + + try jextractSwift(config: config, dependentConfigs: dependentConfigs.map(\.1)) } } extension SwiftJava.JExtractCommand { func jextractSwift( - config: Configuration + config: Configuration, + dependentConfigs: [Configuration] ) throws { - try SwiftToJava(config: config).run() + try SwiftToJava(config: config, dependentConfigs: dependentConfigs).run() } } diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 05ac7651..4c5bd97b 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -77,7 +77,12 @@ extension SwiftJava.WrapJavaCommand { searchDirs: classpathSearchDirs, config: config) // Load all of the dependent configurations and associate them with Swift modules. - let dependentConfigs = try self.loadDependentConfigs() + let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn).map { moduleName, config in + guard let moduleName else { + throw JavaToSwiftError.badConfigOption + } + return (moduleName, config) + } print("[debug][swift-java] Dependent configs: \(dependentConfigs.count)") // Include classpath entries which libs we depend on require... @@ -102,27 +107,6 @@ extension SwiftJava.WrapJavaCommand { } } -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[..=" } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 47397c63..4b0162f3 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -29,6 +29,7 @@ func assertOutput( _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", detectChunkByInitialLines _detectChunkByInitialLines: Int = 4, + javaClassLookupTable: [String: String] = [:], expectedChunks: [String], fileID: String = #fileID, filePath: String = #filePath, @@ -38,6 +39,7 @@ func assertOutput( var config = Configuration() config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) + translator.dependenciesClasses = Array(javaClassLookupTable.keys) try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) @@ -64,7 +66,8 @@ func assertOutput( translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", - javaOutputDirectory: "/fake" + javaOutputDirectory: "/fake", + javaClassLookupTable: javaClassLookupTable ) switch renderKind { diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift new file mode 100644 index 00000000..0283780a --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIJavaKitTests { + let source = + """ + public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) {} + """ + + let classLookupTable = [ + "JavaLong": "java.lang.Long", + "JavaInteger": "java.lang.Integer" + ] + + @Test + func function_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) + * } + */ + public static void function(java.lang.Long javaLong, java.lang.Integer javaInteger, long int) { + SwiftModule.$function(javaLong, javaInteger, int); + } + """, + """ + private static native void $function(java.lang.Long javaLong, java.lang.Integer javaInteger, long int); + """ + ] + ) + } + + @Test + func function_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J") + func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject, javaInteger: jobject, int: jlong) { + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + } + """ + ] + ) + } +} From cbbe172dc230c59b05d6e0faa4e18291caa8e8a2 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 28 Jul 2025 21:08:11 +0900 Subject: [PATCH 110/178] Update to Gradle 8.14.2 so we don't have to keep Java 21 around anymore (#332) --- .github/actions/prepare_env/action.yml | 1 - docker/install_jdk.sh | 13 ++----------- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 9 ++++----- gradlew.bat | 4 ++-- 7 files changed, 10 insertions(+), 21 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 53bb3fd7..0e4a1ca3 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -10,7 +10,6 @@ runs: distribution: ${{ matrix.jdk_vendor }} java-version: | 24 - 21 17 cache: 'gradle' - name: Set JAVA_HOME_{N} diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 122c0d06..8746e3ab 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -36,10 +36,6 @@ download_and_install_jdk() { if [ "$JDK_VENDOR" = 'corretto' ]; then if [ "$(uname -m)" = 'aarch64' ]; then case "$jdk_version" in - "21") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-aarch64-linux-jdk.tar.gz" - expected_md5="87e458029cf9976945dfa3a22af3f850" - ;; "24") jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-aarch64-linux-jdk.tar.gz" expected_md5="3b543f4e971350b73d0ab6d8174cc030" @@ -51,10 +47,6 @@ download_and_install_jdk() { esac else case "$jdk_version" in - "21") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-x64-linux-jdk.tar.gz" - expected_md5="a123e7f50807c27de521bef7378d3377" - ;; "24") jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-x64-linux-jdk.tar.gz" expected_md5="130885ded3cbfc712fbe9f7dace45a52" @@ -102,13 +94,12 @@ download_and_install_jdk() { cd "$HOME" } -# Usage: Install both JDK versions -download_and_install_jdk "21" +# Usage: Install JDK 24 download_and_install_jdk "24" ls -la /usr/lib/jvm/ cd /usr/lib/jvm/ -ln -s jdk-21 default-jdk +ln -s jdk-24 default-jdk find . | grep java | grep bin echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 335b6f40..679a1a1f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64,JAVA_HOME_21,JAVA_HOME_21_X64,JAVA_HOME_21_ARM64 \ No newline at end of file +org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34943 zcmXuKV_+Rz)3%+)Y~1X)v28cDZQE*`9qyPrXx!Mg8{4+s*nWFo&-eXbzt+q-bFO1% zb$T* z+;w-h{ce+s>j$K)apmK~8t5)PdZP3^U%(^I<0#3(!6T+vfBowN0RfQ&0iMAo055!% z04}dC>M#Z2#PO7#|Fj;cQ$sH}E-n7nQM_V}mtmG_)(me#+~0gf?s@gam)iLoR#sr( zrR9fU_ofhp5j-5SLDQP{O+SuE)l8x9_(9@h%eY-t47J-KX-1(`hh#A6_Xs+4(pHhy zuZ1YS9axk`aYwXuq;YN>rYv|U`&U67f=tinhAD$+=o+MWXkx_;qIat_CS1o*=cIxs zIgeoK0TiIa7t`r%%feL8VieY63-Aakfi~qlE`d;ZOn8hFZFX|i^taCw6xbNLb2sOS z?PIeS%PgD)?bPB&LaQDF{PbxHrJQME<^cU5b!Hir(x32zy{YzNzE%sx;w=!C z_(A>eZXkQ1w@ASPXc|CWMNDP1kFQuMO>|1X;SHQS8w<@D;5C@L(3r^8qbbm$nTp%P z&I3Ey+ja9;ZiMbopUNc2txS9$Jf8UGS3*}Y3??(vZYLfm($WlpUGEUgQ52v@AD<~Y z#|B=mpCPt3QR%gX*c^SX>9dEqck79JX+gVPH87~q0-T;ota!lQWdt3C-wY1Ud}!j8 z*2x5$^dsTkXj}%PNKs1YzwK$-gu*lxq<&ko(qrQ_na(82lQ$ z7^0Pgg@Shn!UKTD4R}yGxefP2{8sZ~QZY)cj*SF6AlvE;^5oK=S}FEK(9qHuq|Cm! zx6ILQBsRu(=t1NRTecirX3Iv$-BkLxn^Zk|sV3^MJ1YKJxm>A+nk*r5h=>wW*J|pB zgDS%&VgnF~(sw)beMXXQ8{ncKX;A;_VLcq}Bw1EJj~-AdA=1IGrNHEh+BtIcoV+Te z_sCtBdKv(0wjY{3#hg9nf!*dpV5s7ZvNYEciEp2Rd5P#UudfqXysHiXo`pt27R?Rk zOAWL-dsa+raNw9^2NLZ#Wc^xI=E5Gwz~_<&*jqz0-AVd;EAvnm^&4Ca9bGzM_%(n{>je5hGNjCpZJ%5#Z3&4}f3I1P!6?)d65 z-~d}g{g!&`LkFK9$)f9KB?`oO{a0VXFm1`W{w5bAIC5CsyOV=q-Q7Z8YSmyo;$T?K za96q@djtok=r#TdUkd#%`|QlBywo>ifG69&;k%Ahfic6drRP;K{V8ea_t2qbY48uYWlB3Hf6hnqsCO?kYFhV+{i> zo&AE+)$%ag^)ijm!~gU78tD%tB63b_tbv9gfWzS&$r@i4q|PM+!hS+o+DpKfnnSe{ zewFbI3Jc0?=Vz}3>KmVj$qTWkoUS8@k63XRP2m^e50x-5PU<4X!I#q(zj@EyT9K_E z9P%@Sy6Mq`xD<-E!-<3@MLp2Dq8`x}F?@}V6E#A9v6xm%@x1U3>OoFY{fX5qpxngY z+=2HbnEErBv~!yl%f`Eq2%&K%JTwgN1y@FZ#=ai+TFMFlG?UV{M1#%uCi#Knkb_h| z&ivG$>~NQ4Ou2-gy=8JdRe8`nJDsqYYs?)(LJkJ}NHOj|3gZxVQJWWp>+`H?8$$J5 z*_)+tlyII%x#dId3w(oXo`YEm^-|tFNNj-0rbEuUc2-=pZDk7fxWUlw;|@M9s1 zmK9*C)1Q?F5@NPUJOYOAe`GHnYB%G37_sg3dxAttqLs6Bro)4z ziy8j%C7KKDNL8r#Oj6!IHx|N(?%Zvo31y4;*L1%_KJh$v$6XhFkw*E|fEu9`or?JD_ z13X4g92;TZm0jA0!2R5qPD$W^U z`5XK|Y^27y_Q%D>wWGtF=K00-N0;=svka>o`(;~dOS(eT0gwsP{=Rq+-e2Ajq?D<)zww5V36u6^Ta8YT4cDaw} zfuGnhr_5?)D*1+*q<3tVhg(AsKhR1Di=nsJzt_si+)uac_7zx_pl#t(dh816IM zvToHR%D)$!Zj4Q^$s8A%HLRYa>q9dpbh=*kcF7nkM0RhMIOGq^7Tgn|Fvs)A% zznI7nlbWoA2=rHHbUZ4PJMXf{T$@>W1Tt4lb|Or4L;O!oFj8Op8KEE`^x^*VSJ`9~ z;Pe~{V3x*-2c|jBrvSV8s+*Y3VqFKa@Napr#JAd}4l7;sgn|Q#M!(<|IX1<)z!AC3 zv<5YpN58Fs4NYi|ndYcb=jVO6Ztpwd={@3Yp6orUYe6EG#s{qhX+L^7zMK+@cX1hh?gbp56>jX*_Z|2u9 zb*glt!xK>j!LyLnFtxs&1SLkyiL%xbMqgxywI-U*XV%%qwa5oiufFerY!wn*GgMq` zZ6mFf8MukDPHVaCQk#oyg^dhl*9p@Jc+4Q9+0iv?{}=}+&=>n+q{o z#rEZ<&Ku65y+1eRHwcl3G7bR`e{&~^fGg|0))$uW?B@;_sWSls!ctnjH6ykmM8WJx};hvdXZ>YKLS($5`yBK38HULv}&PKRo9k zdFzj>`CDIUbq8GxeIJ?8=61G-XO?7dYZ;xqtlG?qr`wzbh7YyaD=>eup7bVH`q*N5 z)0&n)!*wW$G<3A&l$vJ^Z-%1^NF$n3iPgqr6Yn_SsAsFQw?9fj z&AvH|_-6zethC3^$mLF7mF$mTKT<_$kbV6jMK0f0UonRN_cY?yM6v&IosO?RN=h z{IqdUJvZd#@5qsr_1xVnaRr`ba-7MyU4<_XjIbr$PmPBYO6rLrxC`|5MN zD8ae4rTxau=7125zw|TQsJpqm`~hLs@w_iUd%eMY6IR9{(?;$f^?`&l?U%JfX%JyV z$IdA`V)5CkvPA0yljj4!Ja&Hjx`zIkg_ceQ;4)vhoyBeW$3D<_LDR~M-DPzQQ?&!L*PUNb^moIz|QXB=S z9^9NnEpF+>_Oh6+Xr55ZLJ7`V=H}@D<70NiNGH{~^QE-U)*Sg@O}M|%{Rcpn z{0nD@D%@8!dE*mndd2g!-q9;)jb=IUED<(Pxh`9B>V3z#f>82~&CVZASC?|;C-VKy zJU35T|3jd(p8F|#n@T~Wh2l1yURI=LC>Uj_!8i7-DE_IaSKIMAx`WMEq8kN%8sAx% zOQs~R1v12(=_ghVxzylsYZum-%8QmjM3-s2V!jY|w#ccP)}OSW?MWhNu@o-t0eTg{ zyy`}x+}GObZC(k>-upb2C6#S*NOfWbKEyReP%gay8MT!pJpsx4jwCu%>7%sY}1L6Vybj_P+;yP`YS92 z^o_G!Gr_NP!ixe7d&82H&achfi83L;le3Fs?u%E*xbeOKkJr7mp=)RXjZF;h*hR<= zP_cs1hjc}0JlHal=enmG&G8wsn%Sm$5Wcgs=Zc}}A%3i6_<4k_`-$k2E5f6QV{a$V zg3VZO36o^w5q`q2ASwJw#?n7pBJyGt3R<`Sd8d|52=h&`|CPq&1Cz&42rRCHNjDZL z$}Y*L+#N;!K2Ov){~fmQM8hVYzj3H@{yS>?q3QhhDHWfNAJ#q@qko|rhlaGG4Qrvh zmHpmg&7YvgRuI|i78-{)|wFx(R^_ z{ag(}Kbbbx=UW42sAu}kg3yB#96dJlOB{+or<(51ylVwpXII7Hrlztq!pefQ?6pQhqSb76y=sQx zOC-swAJaqnL_ok{74u_IHojFk;RSSFfjdLrfqq{syUxA$Ld6D2#TMX(Phf~dvSuuX zmN2xzjwZxWHmbvK2M#OhE#{`urOzs=>%ku}nxymK-dB~smas?Z(YM^>x#K)M@?<&L zeagMnj!XK4=Mid$NvJ+JfSjvc`4rX9mTo^+iFs0q7ntZ{gfU3oSAbK_yzW3WA^`6x zWgPSLXlEVvh!G^fOzZ-O{C_v;V6=;DE+ZqRT4mbCq}xeQ0o z98Cho%25r#!cT_ozTd~FK^@AB3OnrAAEDI4==}#I_v}iw0nhA{y99mFRG*1kxFkZP z+are- z8D|3WoYE>s0<=h)^)0>^up+nPeu}Sv-A($6t3AUedFczOLn;NW5_xM0tMvvrOSZ}) zA2YG1m4GxLAHZ5k>%}pHYtf-caXMGcYmH8ZPLX9VCew0;@Pi-8zkH^#}Cu$%FmKJb=!)Twj!PgBmY0+>VUsyyT}Jy>vMt zo<^5lmPo5Jt-=)z2-F{2{jB{CpW2JDj%~JnP*rq^=(okNQpH=}#{kqMUw{&=e-5;G z!FwJVQTDS7YGL&|=vJ+xhg{dMika2m2A#l@$PazLQ<6$GLC+>4B37`4aW3&MgENJ% z#*tOQsg{>zmcuSgU?peLA}!Rlu&K3LTc@drSBaI?91dK75;_`(V`NHjkMj``jwjJx zcm_!liUxn=^!~0|#{g2#AuX9%;GTBq&k+Jz!~Cc+r?S}y=Q1okG0PRIi3C3wgP8F| zO2jcmnVbGXp*Mu&e#a9Q5a}w7$sITx@)8b}sh(v9#V(H$3GLHF@k!Wh+)kNueq;+r zFtj+^b1TQe?R#Y8{m!7~e6%83hbPKoizd2LIg3yS5=X2HE^l4_|(2q#LB zeNv&njrS$?=zzG?0Min#kY+3A)H1uMfogMYSm|vT%3i<_d9X&~N*ZCL4iB@YaJuo; zq}-;EGx~T43kq-UHmTn!@sc z3bwcs$rp?~73h*uZl_ysD*WK3_PS1G3N^t3U=KoRm_Gz@C?M>+x9HRMk(cA4m&L`! z=Lb~4*9zt*SHJgsAMAcTy*!1W^B>4T_doWvNw7UwmyA=Wq&kE{*GVHp9Yk5goUO;k zVb_3ARrFPG;&>Jv@P&`z%}t!*M|2127pm{S)gs~f_ID^lOH@nIW9DgU$=FjqNW0pv z&GYdoxe@)RAWWx^j|$N}sj*p)_bFpk`Y=NilvsI(>!Z&KBo&I+wb*kM5Vvkkr#;q< z3CobbF+GJ#MxL?rMldP0@XiC~yQCR57=wW_<$j!SY*$5J+^v{Pn!1{&@R-lHCiK8@ z&O=XQ=V?hjM;h&qCitHmHKJ_$=`v%;jixnQrve^x9{ykWs(;!Q9mlr#{VYVE93oaW z&z+vBD}!tBghkriZy7gX7xJp8c}ajR4;JDu^0#RdQo2itM^~uc==~eBgwx5-m7vLj zP)vE#k%~*N$bT#^>(C1sohq+DwAC{U*z(D)qjgghKKSy#$dPih`R09rfbfI-FLE!` zn!tg71Wr(D7ZV*4R@GqG&7)2K*Zc6_CMJoGu#Yc>9D#{eyZ>u-mrWG@4Hk(je3lnH zu9qvXdq+!`5R1mlzWjV^jvaHl>-^Z+g^s5dy49yem$0$>341=EGuOY=W5PCFBTbNN^19iIQ57C3KcV}z~z#Rvngs#j;g2gswC(TLWlViYW}tB5T#g4 z%vDUYTo1@+&zE&`P%fXc^@prE5z;E@;; zKtpEFYftJq-c0sD6lKYoEQ;O1X4uFZZ;3gdgfAKqIc=Dj6>unXAdM}DD*@a5LHk~o zyJjW@aK;XG%qr<)7Rqh7NdUpnTR6jc;6{FKcK_v_#h{IO{mez>^^70DAWB5whqq!J zevvLUotE;I?IWWf!ieJ-Hx`TqY5)ND>K0NCb7IW40Jk*J* z^#m%kIA~Go2=R|y5zM|*ehJxyuX;lOQZkArKVbQV(XmidUH|8U^q`wP(7%F}=uG}U z2~&~CLebE`c%SCdeU(l&hryL~+Y)6I^d@|||6F15IAGo`G+CdVf zc+!EycZnQH)OBE zyTd8k{(_v9d2}osA$*>Q>Q&OB(7ShxA$}p8ChVnYlXl5My$HlVx@ATprrj0}6)ycK zcQy#bwOms1CnS+xd26}k?J;WI{HR_U+1T^I!$B^S=pJkT705QaMF88VJp!s%`?y9z8f$&Xw(A}3u_(n5G{!)yH&zN)S?c1$SZlo>XieJ zyEFa>_p9B*cY){ct8=dq>uQTf# zd4vB4)(ebwQHlSAu}(6GCe28H32pz^}l%Zqs;Yl|B=l2d9HrCcUf%wxLYs4CBqJ#{gz*u6V$>?9IT@uSf~2Rgk6CNw;C21ZbNkm>ZTc@2zeOSXVE^>i5!2>t%!1cI z{FZA`*o4=dTDG3&{v$3xVr%g;3d(!SFJU}w6x_Re(ohlni)I54Wg{t zWLK{A(}qEIH@pamgtr3serA{THlp_IR(gt0CFguk={|Ochh10)7UV4DcnO7fvL<=x z^WCMg_TI?U8(loaUnAe+Nc9I1JIO#_C`=kJG(&wy%Cr9vRFcY9^8{A3A>GuSW~Zk( zMA#t~0Dw?;3^Ue|lhSp4p%YvYmw-&3ey3}+{6Uhz?l1D|6nYNok6?4N_C!OSR=QtS z2X&QtWlkZshPo#-dXBOlSqh3D;#*_`hyohR>vl$W+QC>HPOs0zwHKN`?zIKqCTw&w&NUGNS|abulHe{D+{q z`WvLw?C4K97cd}6V6f2NtfIAO;=c>qi^+y4#oMjK?5Hy9$Tg1#S~Cxoo-Zdpnt2kG^n}`9)Df-Spvx&Oi+6xXT=N*0l|d`p!ZU ziQo9$y}PYIF~Zqh^?6QZ8YS*JtD^gynifSLMlVYRhBi*f-mJFS<>l%5sp5$V$p*X9?V-0r4bKYvo3n@XkCm4vO-_v? zOsLkR?)>ogb>Ys*m^2>*6%Db0!J?Qvpyd+ODlbslPci9r#W>d~%vcU7J_V;#Um1+` zG0>Q$TrOLUF0%a3g=PaCdQVoUUWXgk>($39-P;tusnMlJ=Dz}#S|E== zl6b3bbYaYguw3Bpv|O(YR2aBk?(jo+QqN*^6f0x+to-@2uj!nu6X{qLK>*PxM!i0C zZwrQ}prOw6Ghz?ApvM`!L3Dzc@6mp<2hO0y{_`lqtt!FcUmBG+PBwl?>0Mwu)Ey{L zU;A{ywkT}jCZpPKH4`_o0$#4*^L7=29%)~!L4*czG!bAva#7ZCDR|6@lBE&cyy5eE zlKHwzv7R9gKZTF<8}3*8uVtI)!HE%AZRD-iW!AJI7oY43@9Z$0^MO@Egj1c?o(BwF ziz1|k#WOgAG?^r1 z>+p=DK?cA-RLIvcdmwq$q?R;ina0SPj@;Mus}W_V2xHnYhOq~=sxzA`yTUOsJ`8`VOSTE=IZ!x`cZYqHbgPijF>J>N7( zqbNsHK50vkB1NI52gyb^PflpU0DRw{&v7Y}Hy2>pV@W2f1EOd2j;H?|WiV%2?Dk7u zS(NrEUDl81<}yY9J#OCwM)N?x&PB-%1{oD*`_ZLiBJ=16uR{n+Lk~!t(&9U#>ZfVd8Iqn&idGd>uo?L@sjm>c|Lk z12d3Y>N9U`342@xaHl&Q@oE5V-f$s`04q983f0#m_WF=X_A89W8C#{uCdTNUZ+))$ zakPyNU)?MDayCKxWh0(-v~1rd8FxocW=Dc6B1%N4^SgQj$?ZMoAMQ-35)IMgf&)M?c@}4QG7=DTq{nHc7yp=CZ z1dh~VkK%OTr23U1mJ*a-DxX0Psvh_13t^YcPl9t?_^$pPEhhwGp}s~f=GFR;4@;@f z@B;R1U6Df?yl#Y=BgYTlP&<|8K27||rx_?{s|L);GM3^{Nn8HZp zFqxiG6s3Nb;PW3O=u;(-o(*q!^2i)jHY%N@;O5Hder~_@$zh4xG#-7?#S^-&M~yc} zh5Y=ltLBnTzt;Y%YNqi2d1M1LOz?MJbZ|Nc6>x19&l_S*2Rgk$DhaP7Y-C)4_uPzf zQm)OY)$AFfE1(0SxkbbN4}CHnlU`RqYFGIE7S9ipx_Q0vkE5JRq4Uc%zV7$?y(x$y zV^)5zwjH~+4?xN z9s@x~w`C_cS}khfI14K4Xgn^iuBxkd^u}3cY=VZI@-8iWHolPtt?JD5lZ1V=@g6yR zj0>bd7Z(dw+@)v#r!xpZaAxgT?4Ton(h`0}fkfF!ZDSu{f*r#{ZRp^oOrO3iB|Fa- z;|+PpW5JKZxJ-kjHf`-7ohmnO=a)Xl9lhI8&$)g6R#6PBIN$QSC8kT=4zj?w&=`!qjkCvvz;ypOfR7P)w^ z-7LFhXd6GLrFa_vGLwR5MRvcV*(r!NhQ@}T-ikBGy!fHaiePD$iA{|Q1$kct2`qHz z6nAyERuqvM6i2^?g@w7W2LLr~3s?pBDk6ce8@CxV;b%4%-rXK-GOk+($sSNK;_FBku zm89B}tpzL-x{dPS-IAjwyL*t7N%7~2E)9OsWJJWHc|}BNa5Xwdx(j7i7AmZhs?#zi z5{y$uQdx?O8x3>+5MR05HwUa-YZa*|UVLOb`T)KHk|~Gmwx8MfBUtM|afuM$0wb7m zR+_lU9=W~Y$uNlxt&(@&1;6t!r69A|W%;k3-%SzLlBzc0 z`b?Jmo`8{LI=d|I3JDAa|iK*D6=I_3q?%xFSLg1 zI^!pA=K}l1joBBj8aa8XHp^;Lf`9xNa&Cv+twW&$_HAwZfHrVcNUrRccn_ z1+L!z$k@LK28nc1VB|Fbwm$wO;B~yEdww1EUn|s&{-Tu;@$d94BLL(OQYx|aCa|&2WPT{qJzbNU!ep>j){o5=6le6 z>~Amqs+mCuOR2)aB!#sK5fuui7LsO!Qzl)lz?Lm!QoQFWbNIkfdkrn|)YbSu8WwxZ zO{}a~wE2Cu)`a3X+KI#LHm(Mi+}bOB6@N~H2}Y)e*}w8_z^Sx`c?CWvu*2{K#yqGo zx!Cu*+8&tdw!eiKqZIQlJg5Cb^hZ^Zh~Mb0l(4m4hc1mP&>oTdt7eS-bEz8mU~oObme{^%56|ou~EPOSFBa7VpUZC z0gVc<@IUeo~q)&?o zU@=bz-qfWm)&0Qn@W_fc9{wx={&-#8>0xHJ-+Ijl#P&1qB-%*KUU*DCPkKCLzF*#t z0U_vrk1(&Vwy6Vm8@#Th3J5J%5ZWd)G0mifB3onY8dA&%g6Hir5gqMH|hnEBL0VVvl~aJjdljF$-X@a zMg=J-bI?2LGw-8mHVF7Jbsk1K4LgWi7U>~QovGT2*t^U&XF#iDs_E$~G+t;U;tZn_@73Y6x>vU%x` z6?l`$@U4JYYe#|GcI^f+rsy|MdB|`PQunKSKkja4IGtj9G6buN&ZSnYi|ieaf{k5q z@ABM@!S(A6Y}Sv~YJcB;9JeqsM|-fPIZZfOgc*FSzIpEdT=YYT(R(z{(~X&x%6ZM1 zY0(|PepBl4dK*@9n6@`rUMd)K^^0!^?U-1rrB*b?LEZe<5taFp!NoC^lc>}YUy?5FjT9tFmC+%%DYNa+L zWr)zMB%y_6L{S%;dk6bJPO!wmT=wPPK1b$%+ffWcO8;2T+7C28T?{!96{%d`0G~j3 z)6g<%$dC{vAKJ22nY)fnxlD>P_Xb&@>wrG+ZpfQ%RX=R2kd@bH3N*M8=BO zi|Z$Z5e`0NcU5&aN_DST8O@4v3vroq3t<_5hBX;d)*AJgWPb~p=qx4}^Ms6pgyY`) zu z^|u7XSP^~b1)*61r(}zd!JOny@$KviSp>L|jSR!u*1IgKwId5jmAi2`qe%u+XCTwU z;a62_a~Z}TqDJ?6lje5hblv1f1(6U@kWpc)z|&nRBV*UIieQR{Rru*|$L2SzxtL&| z7abeg@xniYhexYoN6zxY{nI^*xKW0Gz8D~}tE>O4iCkpWn8wt4?S`(Ftv?<8vIvbw z(FFd5`p4~#m<(3uv2+pv7uVC$R(iZuhnxFEY{o}BxPg2nYK zzOjuMR`}t3{8z#zfLXy||4JCt|1nv5VFjS#|JEhRLI>(-;Rh~J7gK{as*K1{IJ%7F zoZnXx&Y54ABfp9q!HDWAJlvFFdSC9}J*llUYXFDN8meEa<0}s z8M~X?%iKLB$*-a}G_$rTh;U{M0vc<}N#PVAE1vQdL#9a-`uH3*cbJZ~u9ag-fny$i z8aCs;3E85mgVK&vWM6}FH9o^WI#G!=%YOB#gT`1^VttnSVf4$YKja@-;zARB-`7v< z*imICw^KX73Gq-go6e?w^os0U0HSxH>60JLWhFbDeGT&Z$d3;9NWy;WvICuoZaKMi z=UvTpLDrtssbhiK&A3EuWf6!)>$sUlRcn5?Pk^OCtvApB=6suN42uKN-Xs7u7EjXh zG|>-1Rp>w1KB%sI*b5dGwFbuHNN=|})sR(dekHBL=>I~l@Nao%H=w0q==`3$zP>!I zmgoBoi7ylm<9Fw6s3&T%wJ%>VQmx(H)!iq?ABhdSzitwHlFNGcBW4sc&9DmTThb^qz`diS`xzQT# zhZff!yj2#rS>yfS5?}{inV5BfcZw zF5uh!Z8b#76;GcBDp7^zWtzQ%J;D}es(iWWWQNA{SvyhO`X8oyNL?j8Afn=x(zHct z7)3c%RKTPAyKS0gwVpGLqR2_%EowBpk>rW}MFfsR9>#2aOL!HKZtg$bAOe+#;;w?3*If zQk=HPWSlX7cF?h1PVE1D>LL{K&Ze4d!#Y2qN+^N-`~RG(O^Gjg~EsZbW^ipD9*+uf$K4Cq=H zxnYj(#+^eUa_1nRDkJJH|9$VB>+n4c)jji1MPz$dV4Ojf;)iYjgw#m+4puPdwgLSj zubNnwfz=z1DqFmy@X!!7D}kTo6yBjVFYT`CisjAgjS^cO%|(B2vzWb5PcrnxTK4xu zm?ZZkCy>+)-K8*)fo5JCWa@}^R!iI}a6OA*S&ibX6V zKk0=}K_M7m$#QEMW=_j=4tDXgH{_l5u?oFF?CXKmk73#~&>ha8CH{7jDKT2WoJ&sW zD1wk_C4Q6m{-YEWeAg*gP5`2Yl>4S@DAbob$M?&Gk2@2%+H*H2wu_)XL3fn{D8ljl zh41$!&_(kR($}4zJj3?zH-A0f2$4;9tH|N9XT48P;?coFH~9`z4S_35{xiUZC4&-3 zo3Yt|ee&RI&qBF zW$mPrwbqtHO$6De21%1=8zUX5=uMV*>#k-H>d5vP zz8OPyI|HLGKn`U2i>k8-dUX}5DJ(|Oy>)cK%QOwU>>~+Wn?bp?yFpx?yE;9q{;DTa$CFGK2S&xDNk$24GuzOgK{np ztsuRfjYmLjvhn$}jK3F_+!AtM`LVw=u&FUIGIU6>0@nqZq~REsb}_1w!VB5-wbS#J zYPBNKKJcnu^LTORcjX|sa8KU?rH5RRhfJ&l7@AtLVi|n8R7-?$+OVx!2BrQCD8{a)Kc#rtcWIC2(YYu=0edjgP9sFpp0=(eKUE2*>jc+n@q? zKTY!?h-S?Ms1kNuRAjowlnTQZF=#1S3XPx<()Wc1>r=QN?#W;6OL z2|Y0fxO0y=?Qi#F4?$+-Qpt&J>-JT?;d6ITN&7R`s4l(v17J7rOD3#Mu@anT`A z88>nZmkgV5o2{_IQ^TOFu9g}ImZrc~3yltx&sdaLvM=bAFpUK=XGx*;5U2#%A{^-G zEpT(GF(}NVJNzn$I*!S`&mA<1j#FEw4`lJ|^Ii?VA+!l%tC)`Q6kS&`LD*!rp)SSZ z!fOJa=BWFG0rWJE<~c2SnT{ykD23&sE?h7iTM20!s3!XMY*WJK_oA3FzU zScKW==wTvjelr=iu2>(0OLprW-Pv$m4wZ7v>;gB4M5m0(gOK>_@aIy}t&Y`H8crZ% zbo1L-*2^hdvzq`~_{<=PT=3jZ#UgMI*bQbOCzf~T53X2F9_QJ+KHwwQCpU%g4AGP z7i4m>KYOFyVXw`L5P#h};Q56X@OHZ-P-1qabm)G~GS>9sP0ToSI#43Q5iDCjG6r<1 zyJZa^U&>SXTW+bvJNB5oHW0xNpCGimZgaFJSb^??Uz1|jbXP-h<65N`CgZYX8jM3^ zSJ2tNSxr8>9)`mMi8nHw1aDz_?+ZRuMO@tou|Q9z11zdD#ka!jZfeXi(bGK&_vVQ^ z?b#6fYLRy70Mb9>3LcE``^rMcoxj~!hvBT%&cQK#L#nhF)C)iw(B$hY1fwak15v#J z-<0Kg=Zh1uk_^yGnO~&Hl|4?14*DFz9!$a(EAbT!5(<}0xUlYlC%`_JfofaWqfWNEfhlbLb2Ds@#m_oKXUJ0 zdSUbdO-BOnM!b2U2o3t3AQ&HGTzjL}LBTpwM2|gf3<(USB~4unKD6^_G>?@N%R2V zE+a}P6(vB@x|W>|ol!d5vws)e>m=0+2Y~#n1%kb=NXlT+^$#v9N z0Lt8wQ#?o)_j$PRavtm~z!aRPQ85^H^}u0bjlfDm(!3xG(oMQY?(DW6m1QdXq-PG; z7jW?rNj(vW&SZZ>B^q=2mU!8NLql4|nTI;pSkw9gbip(A^U<9DVj%Sjd-T0)ldwku z!O)$tFvVGRJnSI!t*v+U;QlSXfMu%J>v5B@Rq<`V$DQ>YTCkc=so?hUx&dda4;A1r z>~5vZ0E0M|B&lv|71*mTuRX`GB3G>9RzF7}+2HIgGrV-?p|bN%&4si|xxb+z1S}F2 zOBQ37uO?>1n_T3UF8nYp?uWnU&+53X|N94hR8WunjZ{}VH({S=x7sRbdLq7vyftJ? z2@;dF{)x|0nI%sYQ|%pe)%r zxP>}6S+ylPH{St~1KGov%?}z^A&&&(B(s+ngv{wKZ_L(*D^+nzoie`$NZ_*#zQ@&T zeLY@LZ5;akVZ}L=Qc=fIphsO^5%YJ0FQWW3*3|ahxk16yr=ZgTqunNMFFko^CZVSh zlk<_(ZLf{~ks&04%zz`tNla=O_`5r6W>d-%mdkEryHLIgIZyrq88$=4=Im4xR_}|) zZ!?V3+6QZ7$+wYJ=>nqKQ2L_gKw%=9`ds2Mdo6`avM-uO$tdP}7Jandkx0}XQhkn# zzq9uFBxvJ^#%sW$s)6J+j5 zXmAN{4mTo60nJnc2C6XtOBsVbJYc5&a0nZ|e?0yj+kThaCezk^Cm!F<|A=cu`uO@u zMai;5H6<@WD$n?-1{?Pzr2mF?F||EI+58#(N9dB2U*+$o$gl7(T>0jTu!?94mCA7^eb%}7cOyZN?nfVx+L$x~x>^tyJj$vmKZOXBKkU?mdopygE`0+rPi zx3F#q)PBC|6M{n@2|m%_24@G{?ql$@S=PPaEh1sG9v zxo35;K!!nAr&^P|c$6z+&vUa@eX|Uw&nednN1SCQSFNx={#kvzFb``4ixf3m zIY=2lKDmS2WGQx#gfP0BOAD4i?UoNdWtRz&Q=#>Y75@;X*z^@rxbLVa`YnIz{oaTE zNGmThd0`N_?*0!a>=f<^TOdF{&|-km!E9iB4IUs0KsvY|y6}%EN>L%XAjjOs+WGAJ z=wAmEmK)JGoI&Uq$`1%&(sh$n^lmT{o9pDd>t(CQ;o9Sr;gFtdZ>-qZg7jbc*P~uh_&U$wOO;{P3h!F3|a}dH-WoGGsXGBvB2c7p<>_CnJAYP}_#gD0t)$ z$Is_In%83bCJkJDij^-Lbnh)JKexs8f3E|dDy=BUEES;}7{*+oxV&iNODhNv#y<$} z=-mY})V@*#j#N6^A*B940E$3$zfmk;3ReX3DO;=d*_(!|f4FL$#0mL1ToWidl)O|S z_mi9mELAQ#S-D7+a2+=an87R;9t|U~1&sgF{`AZ#ZsOL+=sb67R?kPP;SQrDJP#F^ zsr<9}0#5FYl#3;3$mekh_XV=g`LVN$408Oz1ZU^F@kv7gMcyAWTE+yQfcY<&di4?0 z09J)>xHkZoQg!{E*RBSy?JCKOX7n%2$6 z-dzz8T10-8&ZG00yi<2%x`4@L8oj$ZXP|WgZ7E%-(h>@kqIJqt!{ou4J@Anf#HcEw zPSv)TmeUHAmeK2Am3|mkp+~W?)6eVg;c7e2H48x zBw;iPnvFX(a}Y+nn8^W#;6K4qA&N3hg$HYE=n|Dy)1^$6Gxud`0!yZ0d*p;(03ud^ zy^hvb&{_%?^-|c8>2fAn_!5YCX`?Ov6`*x_BAqZdP7`m!E4|c0ttvHBo2}NJT1HQs ze_rYk1e$5HO|)A}>0a7uufbmK{SDV?ndJ&?hXXVWWefy|nb5Neb%C#pK9tl%P-U{v z%DOV=mf@tF5qHo|q4_JBR-PLXOPn6TUrQ#9e83Sw*iIv zU^kn1C|EKWK_mS%Ah;Pks|+@@OxM8{T4o@Zf(mvI z55b=nM5d)6kW5m_Lx%`#@%0J~At8s1=`iJf)}P0CE6_pa-@`H5WIHbP7t4>QJLNX9vAkd8^)UWbAP6$@LZXWxAVbOYkgCYh!Pi4lzTy1%B>Pf9ZYnAH}3- z*{;*nGg_ZWZvV-oB*dF(WQ0^x71UW+hk8Cp_g2sc=tD&+CHpenk8FnaqFX;|TH%e* z9ifj@(1+=xs1s>xxwM`XyvIu)rw0VwCz$GAQ(yL@$J9)4{viA{r49G#c+Z$S3LaiI z8H1fq(Zeb|M4x7oLLr4te=>z$^SG9N2w2ERGL4D=I9HuNqS6>W3ax}f`>ts|P^Zvm z@RHI@6xXbm9v9ry(J7RMY_2a`aPR71XW4B1S$a}He-4?~NS8>v_Z&;WYl>KnqBJ7-hpw*<(4p-DB;Erm4B)LPDS{#kCnL(dCt zzl#E4aVwa$czprcYdPwIDCcme_C!|1U))PSuuI$zk*W(Ap#uWp$Ho58;-{sE*^$YJ zfcvRRKNF?1B4(sbe>9@m?fS5nel8lSJLrFy&YLbuYc7$Di~9RZ6dwe@uT*+bv?gxR zf2UDHLuJLEg$yM9E&WcA_+R7?)37(a^as(%yhwk9vCtzREf&@5r9ab0gl1l{v<@{6 zC3O?M!(VOl{tcWYFh zcWyW`&qG3pOe@HR0(&Pf@bG-DEH=)i05VspTrF}nH!FPJEICoc3S)q%V+;_aFop)l zP;Po#SxD2ff0q4{T+T}wqs1MJ(W0uHR%OPB;l?2?$s`KN)CwvpIWi|N=M^e1V@wxw zhcbE=o-@%8PA~qV;Cea8wH_!IqWp_Sb&NfdNz}9rhH)r2Br^t) zMeQA%TY4kA4{q7j(jMtJ*xS>w>)_TMT^(L-L2JjGxOJj&ZV-)ggVi{5yFFtT>@y74 zJf{=@f2D8cEh09yg6#A&72XCLgRGuD?B$3Jh}mU9;ruBh4ewxD7AzgZW*I&BN(>mh ziz!$}F_R7^NNhzIC6VZOw|xa*NB`8Izi`@_wbT62%UAIpm3#SWG=pW%ix>j~;()!P z=|~#* zs~lrgJ~te{KY{96l8>ex)n>uuGMb%`c#snwpktC*Tn4EfgILng;xZ@8J7YPjGNU7z ziy8fhkvX(Gk4lucz zopwj%<+s`80do~2D`Ae3vs%C2n@KP&f1Tw*W`gvc{0^aDj8k(=qot>B`xmPR?nWM%F_Tp@8f$^zMC-x zxq5eR4y{vI3_c*+I&2E>TUd_fzE&@Pkna^rKrwaahT_Qipb*^GDr(jJ{9!?Jf23IL z(A^If6~w*; z?}1Z(f$4(T18(_hnK5l-&KgXmo>nd-3e?K(mCc5>6~3tQ)BGjdE37LV)Q^&pwQ#S) z&+u1NlKHDJYC|%1Na3%+nyEu^jPYK6&d&RoKPnRF@-yfpj11b3Z`tb@e>%>eq_``W zHjyW%v=QIIjMQf2l5wjwh-GwmTwut$YYW7S)B^oRCLq)v5C#Y+jB#TgxNhmo8p)ig z+m?O7x>V%vtNgs^JCwARHbhpo8tiRe{t^FJ)aIYKNc@@Cy2(NO%_oXe2h_a_mDEVt zmb7j{8H0tCIim0{RsMyjf5xg%)u5J6>nIZ!1*crg#_ZLsWwQbZRQGHCjX?b^(~`4- z%8a=}HZ#K!NGa0IY^23L=>CEKsPgamPfQ#BAATw`rjrHMokCmE$m&;$>$>FdWOl&m z)`l3}takOU{5O^V!Y`N18@mT#Hk8i4BUNORx;`YLf13b*mCvaBe-8<>i!%lf^-2;U z9Xu^Lie6DxK3T%#A{V~ncqJJ#j^vgU*fE*tQzR9Izl^818it9apbd#{E7lZ_VRf}E zc~xnS$S$5Fa)vkpeqLJ|acM0jlw*p5vTxcoxin9j54VyQ6lcuBR|hLNBB)YOqvR9U z!GXe8h=^BOD85uIf0M*0GA*2n7=9$tiDqrej<}AS5rg&?cv&o6pi1XUOT5%!|GH4f zvaj?*$t>7b&`TGoQk8_MWDe?v2r}Dt(=V&+RUEinS|JRG@uWH{KKj7Hj+!Oxo*$h3 zJSiyE3UmxBOJT8wLQ9;~a_QJ0+H$+Y7xq%5dSM}87BbO_f7fWu3%N;ZkQ#*^Fy;8l z+=R>08U>@C^*y3XHwO(!x~UB1eKROeJu9R4i#yRqn*t8KOlnf8LRwpLV^InvOY4y& z6Y0aoAta#nWk$@|ua--OGHHW!xhjPv3`wq-h()h-g$Rf$X%kb&Wa>o&%jl;Juf;h@YL`0DJV={S3<~|Q zxVKlNt>PnLnaimuw=2>%bOF+Krp5q#4}8Z1N3?_qAS?S%)arm{Ww3y0Sj8X=>X^3N zqTq|)7_lk>iEJQee_T8ouuaPZ z`ZGo<5HsR>A7m?9YOlD%ISXt11#1V2EoPx>=owC%+R@3XD;+F;=(T8c8;0RJ zTsm&wf4E6n@v_B&nSvZcHW#06QG>Wc4M@NZjXq_R6tyGE%uPgmQ2BjdC;x_^K7e<&Sro+Qon7}Z6ij>=e%vr_NLQ=+o& zBpJok>#>>@t9yzoIjkHJE78hf09L;KB)w^jj*Zi;(XexzZjXje(A)F$&QZE+l#Y+n z`=Vi2$nPAb_di1SF@@cJ_apQ%rsI6t?-IX1$@BzBhvht-IL`O`<;uJelNOBA7;pvZ zfB49mXR!WQo}M^PexS)v&gcE|!8|>kr>}-xBWE7K{@1Mi2C+ZCIZxkg5`fhJ{k9ES z?Q&jg{rY^Kz9*250O|V{Qa~U%CqezPdlGEt!}O!OX%T>bVgb8HsA8Oc79FMkJ{1BQ zAj1lz_A7b%#c`?Pf$=T5(=0B&}8~QNxNwRw*HCGxKs7 zAbuqb0wZTm!A@E!voDKNVzcs90B98$d1mpu$?pVH>>OjYdz|h7=c8OvnalIse-rG> z^TJ7MQ)h{-eY_~oi=$1-J+wg3^YM~AU$kfB%yWKA6u<1KR)jRN^V))`t?f_yozaju za%E*q=!xg(Q{=;$gM(CgBtI%caf_(Rsq{@aD+#S}=pC z86ka~*GGN4VU#aFW&hkLem=}?e|vn~F~*%Z>oir1(1J)V;P~B;pF%#~KE~a%?9Q`R zT%aOCGZYoCbw1uX$~|Kog$!cB?q~!dDf0Qo*L&^G+IB- z%c7$kALW4)e5h-jQveUupWrMkF~&y@j`9uT{Dx>3B5#~;1W8xjD8D&0f6BK2KH7bP zZxi%s6BzdKTl4((Xp?-8aO}B$ceSl^VLKn+QQT7@lRQFm{BB3JY*{801(`8^XP)m0 zD?Wbj7{5On_W1Gh19`qL&mS4*kHL?eO-i0WS*?JlPt9MR=TBSiCFAu3oJ*WezdvZZ zSy&eKQ%>+G2tl=09#H+Rf3Rl+Zi1CZ#ESIpy09nYSNtA9DI^G;;Ll9Z5|JT@L8pS6 z=LDaMhSef9kKYv$QmRE_E9?E9x+#R7EG1O<>7Jl@f=`e0)6s|@lKP$XQ0bTR{H&FQ zqg^6St}cX+CEqrS#MdXVu^sKs^EdCN)gfU|nuEu;t&|cN=jWpWf4BaikH05EkAG0a z`{60><}kwSr&av3l#hRYOk3;XuMV}FV=&DU*-9CmLvT+ z+WizQMWlnqEBL#Bo<24v@d&Bg{c`sRFGPy!hJDXGw0(p%#G{63F=LblwcdY3eAs2Vm zpQhd8QdM++1Q6AEX;GK+F4-R9ZGBt;ETo9?DCrv0D+1IDFD2JwEAD ztgpk0jFnYAjJJ(@@>0vEgx;*>?T$KtwXGVHwg{EYV4k~Ae-(8Mq(-WYZ0p$a#PooH1&29;1t$_t9$S2(58GNS8RjOP4xdqRX7GP!mS( zwXWr~Th0}t^{$I4?CPWqt{rr_D@Dz&!?e*gOjo$xOPgE|Qj5EaTHR}@&3zZOyYHqB z_w%$_-a=dCx6@YnYt$*fK-=U$L01^rp)ZLX{|8V@2MEVi07E4e007D}b)$q0%WLwQzAecs$;-Nd zASxmv2qLK4kS~#nq5^hlp^Wh%1BQZAKtXf}4pBfw6cmwp&P}qWT{hR>FFo(vkMniU z{hxF9eEi_U02Ygt0^2UTZ1s{$s=JNge?~JFs`gh0d#dZJgLbsfiWrV%$9z#cWYT!t zjF?8kq{&_*;S2Vf!HtPzG*RvEF(L`GzPc~$iyD1Ci)C~-H!lhd7@Lg7h!G1np548{3_1!t0yE`k(y=0q zK|2;q#^YwpX>6fwMt8(ipwh-oMr2;Z4jPg3t-iFjiEVP5Wj8W^l0Y%930Vneg%uYl z%W`q6JIRq+8;=~^6f>R1wX0ice^UuBBdtAFI2o4_6~UJ^kg?F#!|# zYr2j}n9N@@1>7~fuMD#_D5w%BpwLtNrqnEG8-Ir6ou2E2f_VZH!ltvzf8c{mpVs8; z#;m70j=`}S=A%Yn>Zr&LhjZ?R7!(;@XXOpGy-LRkte_4{1m@;F!7*B7==^LD=cSdP zjHE!>@hvj2=j%8b%Xsz_e=^rfuoNB3(?h2TOd@BOcPH#f(lJ*VPOpv?Y41)Ks62d1 zDEI_jNFx|D6O@q)DJR1``t~a28pcUU-Hb zr2w4G3E7TSV_>3VOTsau3RY9(%sAca@`GltA}bxT)ik1H!5XYBe?kY&r90kZSdnDh zJd5IBgehf8^CirA2(Y&E2`TajRIr|su8#*Igb3yNQi%@vQ|Qug0WPFt3=sf32k5POw*CcHVT&e?km<5rfT#*GFEMn@M&;M?CEXnO;5$&MkH%LTOA|6AF?7MP{_m z+0sTkD8^Y27Oe4f``K{+ti76n(*d037~VYDfUe=5dU+nO0CJFdc)it$BU zO%5G8uizR=3aYQ|=4MC7SFo%Y*Wx+?$Cw=WD(3RQ4HU_UDH>}?$Qz?#n3%XpD7%RuqWbW)B70MGJctpNfASD{o7H++vZu$4o1xXFA?ww{ zbWYj1)>vOM11H((N3yjpV{pzA1&`%9C|O8;qTz8oAyBw>%}U=A6;BG(jxNlRaoAGy zw1!8qhjHlOwzNr^`JZaog`d$CAt|9Y>il#($06H=pOe~P#7@x2FSr@lgz zs*2f8e^n2IOcmXU-YNne%Gnnv>GNc2HZc_ZisGIydd#(P!m?R4 zivLigs3CR?D@I^FJ=eFEUL)RNUX(Or!8C~c7a#Nf0~EDxE0#HPRnWs=+UPC{6t^VV zf1XabIi-5(-Jyy?!mSgUnpB~XV_Ytcm>sjoUU_Xrk!*W}#(=%bsJCjxKxz05sY_ z@G}Yk3Dc=EH=Dtv!#Ajku0+&I@M|%_fIyc`EM&DL*fHD9e%b4a#j?E+)M{6be`;Ty zj5$`+JbiP}?32xoXwpP8m%f=<^e{tJxy7oghoq4Pa<`(&N{~HO^qjLoRa7tJT!Sk7 zSsgN9G|@;e$Q&I@$3Q{O#Il^uu=VVmiBk!-Mt8Jk<70+$)=(E;&_XY3YUUYE+mq35 zGroo+M7UH)O&>)Tg_BG8Jq8ffe>0TcVv^EJOj3He0dUd!GEAWt_X^@_X}^c)tlGf( z_1=OVsHoe4Y4tl$>Dz%B-ohQ2HH10$f&WTSjk)Q4h1*FdNq1jYJA(Ovw%S2VOJTtX z>H@W0L#UVR!W51#ZKi)IoH&G~gQ!g5)U9Z$OQB^e8fZ@i{VD?~tQIWX*I2w);@?C{sP+OFC4_IfZtP}LT~3FqJG8Qta_S@ zd{Vkvu5N`^@ADRYnG%9GerFINTpiWH}CfKwRa=su8@xYMtWNUdJgtNAiV;Y+Vvf0(n9&Vd3lf?a|2 zyyMZp2p%U3hp@Z!sUbWwglALO>sM2F-mChR0km_#io86qt3HtRNa-qlkvtm4D=F+N z{ry3=vh!+J>Fd(tHxEt;zf#bwmKV7$3^W(rBK+m*wvRirDL}s&QrJB?i6Atd4)_cB zfJ^^8jKAEEf28nXf9Xdl4z_0iFG!aQePzN$eu?%GQ4sL##QTAOx3DYVE)$-Pf-<3Y z6gGQOqPX1C)iER{rbH=aO-fALiUh}@oulAayfieU^rNVS(J z)mTl^2~@tAe^!b)l2(foB|TZJmNY8*#H->Iagn%6(yPU_l3p*iOM0^ymh>U9SJJ)W zd9fc5FN&8WzhAt?)OC&PM)w4HMnSamqf#jJo|Dn53@=S?$ zm$)mKmy~z{%+m=xH=vS$SKv$n;7+))4h8h&FQj*-2UijZ-vAYN5vYCyO)N(-fvhgV zm>{B<=vszJt~HqKx&S4vAWB_fl({a&6!&VByDvb6JBX?7UQBaugx76LJ#Go~?*9Q$ zO9u!}1dt)a<&)icU4Pq312GVW|5&xPuGV_G@op77bzQ0`Ma3II6cj;0@G{*_x6$l@ zWLq!9K8SDOg$Q2w06vsBTNM!*$jtot=1)l8KVIJeY+_#EvERRF+`CN~+)~_fcio`v z*4!Y8Ql(|4lGuxq7O`$fleEN}9cjIwL&2@>M%LYJOKqvn8>I&WVJ`e@>#4mHnuhzUW>Zd%6?zt$4SI~lcxhl zC4TO|$3j~w-G4Q7M%K!ZiRsf{m&+`_EmNcWDpuKnz~ahZga7dAl|W%-^~!;R$uf$l zI4EIk3?ryIC}TXYW(0;0`IS)TrpP}tglbN4Rm~aBg2TZCuXEfjpuhoC)~>H#Ftz@S z>Dn`9pMU{c7+4fO0Z>Z^2t=Mc0&4*P0OtV!08mQ<1d~V*7L&|-M}HA1L$(|qvP}`9 z6jDcE$(EPEf?NsMWp)>mXxB>G$Z3wYX%eT2l*V%1)^uAZjamt$qeSWzyLHo~Y15=< z+Qx3$rdOKYhok&&0FWRF%4wrdA7*Ff&CHwk{`bE(eC0czzD`8jMNZJgbLWP4J>EL1 zrBCT*rZv%;&bG!{(|=Ze!pLc^VVUu~mC-S7>p5L>bWDzGPCPxXr%ySBywjS7eiGK;*?i?^3SIg!6H8!T(g4QQ%tWV0x-GTxc>x`MRw2YvQwFLXi(-2*! zpH1fqj&WM*)ss%^jQh*xx>$V^%w2Z&j!JV31wR!8-t%AmCUa;)Y-AU<8!|LS2%021Y5tmW3yZsi6 zH<#N!hAI1YOn3Won&Sv+4!2kBB?os0>2|tcxyat=z9bOEGV>NELSSm<+>3@EO`so2dTfRpG`DsAVrtljgQiju@ zLi;Ew$mLtxrwweRuSZebVg~sWWptaT7 z4VV)J7hC9B-cNaEhxy8v@MbAw(nN(FFn>3184{8gUtj=V_*gGP(WQby4xL6c6(%y8 z3!VL#8W`a1&e9}n@)*R^Im^+5^aGq99C`xc8L2Ne1WWY>>Fx9mmi@ts)>Sv|Ef~2B zXN7kvbe@6II43cH)FLy+yI?xkdQd-GTC)hTvjO{VdXGXsOz-7Xj=I4e57Lj&0e_C+ zAH@(u#l-zKg!>k+E-Qjf-cLWyx_m%Td}$9YvGPN_@+qVd*Q)5cI$TrLpP-Mh>_<6k zysd!BC`cEXVf*Q0Y(UgdE^PYo5;;FDXeF@IGwN8mf~#|e4$?Ec!zTJEQCEM2VQr*k z8Kzplz+)oH5+-jyAK;GP8!A zSKV>V#gDFTsa`xXt|1Uc3i&PSgl%D=JEwjW^F5vD0l6G!z|~>y03#T)?a;@!*(vAwmBFr?|-8vt&)jK z!?QG5DNz%WTH4H>vbUDpIEl_O19mVOmP_8bVz-kCsYEtX_1Ovb zj+KS444hDHKJfNHwq&hQ29#QGU>;3P1P+D_kVfmXiA~y=y{YGCGep{s6iwTA*ge*SZSH9K;{Gc1^NWT z@{>XOdHMwf#oVVr5e4%x1I%+r&CEE*Qu8V$tmu5mm?%|OR}{L++~wCzm$RIp(7a-4 zuUW|Jw)8G^n5G$)e{tS^RU&@6hKR!RWWQzWdvkgoyCMKT%caX_=zlus#?;Tc<%xwM zJewbXg?^RAe+_wMk=A>m=A@r~0~#Z6hmh`q^b!Z`=jde+%aR2&hxQ>`<7bXmDk+!% ze+$*7qh)2_^In4P`ktr>O8z!|UZGd$clcz~c=h>Hr~z=--z_oAmq3RVC-fGwS&sJu z1-B|M{Jx;us@*hy_J0o)`U?9cH0RlBfikrIP@yl=AE9!T32=5+P-i$<+jN!7%+FG| z&!5nrvTOegUa57UpZ*+hJA>p2ga0MxsK21E^Uo8!3b{#gdjViLw zDj?{%qL2b=fc}>G8S&udSPszN3la#if5csvd~EsYTU;zzV}C*VHpkOH)4w1W41*h( zbOQ8mmEBsPEo@ObLg z93$OR0O5mpOQ~kA@~zx=sm%~6;&yQdTLO>ECg3w&$V;K3Rxm$Mx#E3$#)AP`Y5ET>GF+K7Ons=3AJy$clM99)e@XPVK;DaXeI#{!nwqZB>eS#gwM4Gc z+UQjZ#jeu&%Mv~fw1GC37KsP2q#o_EXrxGY9xc+Ai=@m@d~k~Hixz2HYVc*MpSt<2 z$TixLN>0<8uJ7@5d0V_2pQVkF7Vq{{!dIm33#3Ft_}G2)yjM)!d^I{4d6C{M=mM$U zf6tOXHRy?rH1$Si=)u8jv@ewuk!jjLMIV6_5a7L3EjF@9Y$D=$k&f1(*4c#dO{r8e z(v+H}hoI~Q3P)vOmA?n#aMPBi8^%0|sj#w@`5rIzh zQ!tSbr|=trz3XA)gH(s7qlZqzSnr3Gf1k$a6s-R${PJy>^CsjPC{3BNQR^|!p8G=V zW%6Eb%Fa-3=o*=+gf}`(Z);pdp9v&gz7C z*}oPKd5d(eNI!)2=dpg8p7eD2T72>A&r(Oc#kZr8Zl0T=_oWh8{A0N9vXFPxf7T*> z@F=#&(1(wn_rW1wit#=dQbR@h$qP^^nkv#IIQ!Y8pN*0_p744iBi`tUFE&yiA8GoT zkhf%^=TflG&)tw(+<*mIXdUgu%{CxCbK8#JowN2@0SO=M^#R!H6?`{v`CUe5FJ?Sw zyCTwGaWuckZrbd*cS97n*}$HSe?&KIhht~x@pz>vsk20GwyCM?#|=m*99Q+xzrHv4AaMp^qVvE1qqxlUZ9nHsoy&~b@Pi; zbSxIXMqg&hucX*B)AZGlZ<_wNNMB2M8@&ts^)Xsm@z<+UH@_KAm7Vk&fBsM1e8*q} zC%twfR;0hW%s)2}p$g))S6XPbY}b-1+g56mZJ4@bdpGTo?Oxg^+aw*3?Jyme?QuE* z>k?^{mF+lLvMtd2WXr!S_d)uoY)gJo;16IEvvuH(Z&YlEF~4MtgVERw{mtdnP$YGQ zLX5QNiKcH()87Fhz);gaf8Zxp{{AQY07^yr*Rp8*MAN@Z(f^s9xq-6?{;3ChGh2NJ z5h72l13;O%#FbbiB|~{IS`?nriNJPIz>*(s7WJjAq^m9+Eguv+(JTTuX-2FlipGi# z>xbCfU@qZdcZ!5pBz#h2ErNo*n((t*0g$h4ur7sb6@-iGc#L$?z0#Uu)Xh){P%^cBVZ7wOS8%9=n+@X6!d z0j(RK8a`Hw2l5S1eVl@8los!kPhF(7@ijcCcL%PBB!<=~MKK)m$2=`T0Eu_#R=NXI zH=h{{`4iqLa>{Mue;U1>Y8Hp4#o-&#kU!*$UlB)|#anUx3hcmxfhe0Q0&^ZadKv7! zbC8#@-C);d@h~h3LJ*D3;sie9@`|I)B2%(-WLk{fsNVS{3NYNyg}nR)ue=tyK_MEW zlVVgDvV8=;&C^-g=a&0t>2a|ceQr0P|8{y#_POQ$^YjVXUgwtkpQOvO&n@>kdb!Un z_g|vV%RaZ<|2lm`_POQ$>nH%Z&n^1GBO19cTkgk1x9oGv{j_*W>RF15CZPW_^!Tj4^T{T!k9N#2;RO7iBy{i;&QUo$Tz+ znfE#GOwP=ozrTJ1Sc55We021t`blp}YoGj;%5y1uf!uNG{2U zc(N@c!)lX%wI3y3q;Kp>H=-52V;i3A7>>%(TwkwPYfo4kR?qm|#C16kwWU$vA^EoB z6NQd%bM%nHh`l&oU46V-HClA2e;$PpNH>BcwCIK7lE8cr+NK@KmP_V`PLn)Sf8 zDbz3|Fu5lWrRhrFHeWUO$ci zK|;QNMYU4B-{xxq=2gh0MJ_>CzIO%I2C`dQ0}U%zLwzhCD9eXj_~Pck%ya+e`Xnf; z1j}62O+JMJ**YJ(mx~=JE+{p9z;saHl6M^@O>uaJ(zL_pbbfg95AEkMI{P zQrP_-wu~WeK)#DjC~RTz1jWl>>J%&u_A8uVH0UJwtHj+O|MgSsVS$&sSO#aG3~yMr6^X${<>0 zQle|Lj@}|34Nrzqkl>m>`@k4<9*UKfc&#)tI4W!!rdA{x!$&L15^Z=Vs_fD^%wvtV z4GjkS3$YfV7A6gE;|0p94J`((b7fR@!QilW^Ak`-SZ_W1@A@+aUavpvf)AYzv|)!q z4VaP^lJwjZ|A#8&wqkPDwLy5?V^3lqxn2iXkLKsKp3v z)lw?h02Q#9dcl*)Nir~*8P80hEVZkB@JF-{`qDZ}%ic=6I zm%FuV~79YG9K?LnO!Z^jy-SC}sEQ=yjZJve> zhLEVZ{w5(ZoQbyviJ%i_b(}#LLsvu9$Wy~P3VYSGP5*j5?A-{?qgO|N4=ynDG-o(t zyH$VDmx5O`yrrVG6j*nCTSp%*G6XD#7Z}brjGFxGwwDl7VfqSEf=l#B~g+q=IW=b5Z!M<&ucX9YRuprWo1}sWhaiRi-Z__Z`V_?vU@yo}2(i zFdD}DxXjRbRIlL*gGOwBofG%{2tGu67-Ps#wKfT;#rvpD6d}xUOenjnl!5P12Z*7q zw!2cYy^fD{X!wL7>>Y4wID{LA*tcu0;U>}9^SSiBWz#PcPvS>06_ak^GaXZyW_ZJ^ z=DocXy5lp)=I}XgE9)%v+M=maz{HH12<9-a6nE%cQa3OVKU(g8u^m{zqPmtPawHNk zWR7wCpHO$PtcdUx!|AF`o4_oZJa38m07T<0{69Jm_wcovhi@1zG{6_Cwr^I%)O|y^ zYO*wZw@?12&fKV)RzYoo?-}~1q;zC-qb%&GVmhg#?!i<=i!>0|LdgHijnpTlpo4>E zJ*c*hO|z2vk8U1+%7RKMp{yWG^+$Y3922QYvQ(DNhU(N_cuU6$Dzv>0=5xNOeup?c zNo$t6oTaTgSFPlQTvG0VOE^gcRX<`ALi8~FK&RITk_PxKQN!sc(4M3F**1D|x$G9+ z+(ut+b|{%kY$001J2kwwjltaQEs*i>3w*#Zn|y(f7#?GPoIb8Gtu3 z6l++mVQpv&_A5%Vi@5j`T=XJZe@D@ehm?9h2I}XB_@(}4kR&~YHrm3(cAUT?`X&;S z^aR@e0Z>Z|2MApz`fv6F008!r5R-0yTcB1zlqZ!0#k7KfkdSS=y&hcen!76`8u=i8 z2484mW8w=xfFH^@+q=`!9=6HN?9Tr;yF0V{>-UeJ0FZ%A0-r7~^SKXVk(SPwS{9eZ zQbn8-OIociE7X)VHCfZj4Ci&GFlsOiR;iIJRaxoGXw(dGxk43#&53m>S)=uTq|9>^ zv)ObhvxHhb=kS$=qTqy4rO7l7nJURDW4f$LID5`?1J}a&-2B3PE?H*h;zu740{(*5 z&`a#OtS|ymO_x%VPRj~QUFfu4XL{-O9v0OB=uyFEst^tz2VT!z4g<2#lRmMJ`j5ZM7xZ*AM>%2rvSpe(=Ig+{%mm`qu9D$$nuwfAVtg)wU1D1@Oa-0qBDX0)tL}srdd3AKVr| zu!4652w2`d0fsD36d(v8?%fw448z=eKw!vV=GK+cg<@B0$2aAJ0j^IF7?!T;tpbe1 z;%>zpHr&Lcv2JbrpgXly(as#!?0ARvZ(9Tyw9dPLBI6nnUO(iIoc8&R_JI|#ma!w& zAcT?E9qq-QVS__Pcf=Ea+u?_rKX*`?w+8~YR^5P4}7sOkF z9^v<)Wd+*~+BRU@A=_f}TNYc7Hi#bHH2iMhXaTblw9&-j;qmcz7z^KOLL_{r36tEL z;@)&98f?OhrwP%oz<(i#LEKIdh93L_^e1MUFzdwUAZf=#X!!zWeTi=n`C^CXA?1cg z9Q>gxKI!0TcYM;pGp_iegD<(`iw>T3#itznkvl%+;5k=(+QA>Y9v3?#|5p?&G^NcjljeZ~g^f18y^%J9)Cd^>|=NijQzL5oim< zlYvkmuB9`wBAK$LhSPsqg44Xt6)qW^7KbGx93STK5hI&60&Pi2F?cADNrlr=CM*jZ zLoF@q;~O@SuHKr*C$ow|6UMLxJIZx~e9?Ss^Ty`ZaDtBpPPoAs zJW(yH$N4T<;S2#yPeoF?lu&qNOqVhlu1EGea_2aYXH89ap^|@L(Gh7>iYStriu4X0 z;c?T2YBH74HPSR?ZZItAvUReitVH^z=C?2`C}=rO7dV=-77=68sE%uDQcf{6cFi77 zhpm&o07Yne+0~cxtd5_*)sP&)@HC}ize=e%9 z#0xj(imzo}crbrYe63*c7RTYjDhiU1%Z6##t_Qui5BGbp8h+wH(WFEnJTC%R=pic) zGR)Vxl-NNqUE8ZG40R2ST?P81rl{~1FV5^e_8Pg(x$FW_6(mpMLKFJ(*W5>({#DW*Q zoCKbj>CJyx?{us_MShE|Mu(*hn_8mTv>ROv%chy0TJ@sGvER$E`JN~loQ0D;f|Gu7 zWz6bozzKCPos?s8CQ8kPJJs7yy@Vnhlrv7zVopqhG;I`3KjYvJ7U3Q84o~47P9z6E zG=+Dj6AqqAR72W5+#J*NkpVf)wXA6$(M~T?7#4pzGDBrUrkr3p#=R| z)ud>4j>mb%X;#lOggUgWlJKjV=@*U0pX+Y^LM!$sbuI0$Ut`oayK%Cl!#hQF;YI3S zNlkxGOJ@1oTeu+m*V=%8d-n8%+f;C_H)8o;-_FbP`qm5+m$!#sUS3~az?6UCnEncp zrIoW1GYikZ3^9(J+*73a_E2=I+@yTZzO&nHEt<<$te&=8HKwBfgjml-JG}$lI=92@ z4z$bd>F@tEaq6laA2^*uV=f+<_SYxIZ2lu1)15Avq4jrv%t_4M85a1jrdBbg?&OBO z?w|X;yr%s=o>F|n{!ss|&@a-Ga?>Xp`Tt1WnzOgFxn}QvF`pdqH+A0O6M<{R?*8aI zm|Fe9w=3;hq}hV*9V%VFm_Nouyj`+eMRi@5yyP88PxBQT&vbZ!!)Ky@-W>G*(aL2R zRrh*#Vd#O=-{*82{_t)2Q0>X_c9z?Dty^;DE4*(gK1oaCZ038&qGr3{1N+o{&GW)S zR_RrFeoeXT93w9WTJ=k2WmwRsyZJjz~raN31L?*7OZAKosxIC_$obw$Vto-F(G};KG84}n`sf{TwU%2wY3la+hh1Mo zOk8XAThu>BWiTy&7qj>ZQ^xVsJ)L}CZf)Xc&#mN8-WF1DX4>(>Q`45ejQ0=-ZM4zk z5L6XanSS@s%!u+}4U5KdXED2N1@ELz7MFYE%Vl0?GTZp&z)8j5fxVV0(M{Jk-YLI# zD7^e3@2_*4y-s~w)iFmb?A6PWbS|JU~kQ>A{z z<#_KpR{ZVn&J%Zz?8+_T3iQ3CX&uXK`8Ms6*u@`B+O_xJ&pYz;K_cUp%GV7lwA_XQ7h?=EiYO%jA1g4LkyE%H;C7 zPBKh~SnewUyI}=DY{&pStppCf@lAGIC^PvppTgt~O9f-}d3G+pn zHcEm8XU#X20bkb$bjx(06{tEH6~T)57MRE&F1=%5uthQcpfXUA=H!#g@?du$?pR}B zus~7Bs}5H9dx4fr4CvY|pq0)*@1y!kP7|oePX>Iq6EG0Z0Tmgcm@-Wp?51-IwPcVl z;ju?iv_==K$b6Bx4B|cu^pKur092#|ys(EK0ARQEYY^^{l%|QCuAjeEkp14?q>9h4@!6nkbbJ&fg5yu+?X8=+3#!VJj5-STn zB^PM!VxULuP~>AB87AvHdVm8Jad0aGgFcF?DbAA>SBOrobXEl`gda@_j7wDOI$XgD zA?Lm7ffXYk=VyXqs+K2Iu@*=nEBNf4$p*_rnW}xj5^+A_U=u*+w%i1|eiP93x+o@C zhJh7Ihbe;@`y&KjUXYgX_u)8xbzqD+z9U^n!xP?doXqyT+|nlWGZ zf)zbpp(6wDM6oe2=%E;$(+^UFIrO3?4Q`17gDC*02i4ujCr@1I$qFe_?ym&yj++j) RhRK)Bhkwq`;Yh)md4RrtR%sNbw?F7+wVN@9oT5^KvyxHCChVwDz29-_(~6`YI}kOI zb^sOR2x~T#ZdIJ>Rf@`fWMMck8Z~Fk7!ymA-q=^Hp5eZ$X)}%69EWv#a)HMQBo+#f z36F86&q=PH!h1hfL>Ol{cXt`zy7GFq%Eq79O{IA-u!cH*(wj1wN}D2M4WT6o(qxrW zEB}r}@-+r4&wIr;xO0(AI@=cYWb?m21~K;0A^-T{gEQnxfCN&@N(#Zq#RXZY87O0m z;t0Wp7M~;I&<5qU1T+?pjfUye_TixR_f>$?rT1}+*6u;9Gn0cXM{`4grB6(W zyBDpHwv$&%UIzt(jZMh^e3jZ{I@kE301olpI{yj0+;ZWogmFjno1+v zMW;sMFf7sR(_fhVjl~QhEC!kN?S1GnQ8&fuPw9z{5eDbyAAsT&CyjpUf=RK)X*YhW zwf>HLeXJxlm0mFjo>lB@ni;CUkg)*JRligsG*5>@wN*UJvbS&X^}x zn@^UJmJ90QY)d4OLkji-vg;l*>VWz+eRS?0G0Bg!HhZc?2Wz}S3kMg^_@+65nA?uo zkBwh=aDQVGH8XVK>zh0u{gJbev&iTnS1h3p(pF$?`aC^rhJj2lK`5&HHV#_?kJb zGMSi_SJ(*5xg|k>>Dvgt0#5hN#b8)>x5&pj4Wy_c7=p-XQ=>p*vRykohWoq+vj1uk znu?X~2=n2?uaB_*+Lr;+&434q#3lhbD9@_k1Te#nwy}MM^TTHt=B7p23Hvw*C##@< z$6AnfJ+Ri~X^`J(;3$v;d?J5C5U~zQwBA9#k|t1Y#>7ZrY#I@2J`|kfQ=Sxhc*rH| z{varkusu6HJ$Ca6x^v$ZA6sX;#AVi73(ebp61*3)LCF6yToc0LMMm{D%k+S_eJ<3CTZgjVEpgE=i5mX z0o|kFlPT7$0gM?NfN_Wk=T=zCXFhtz_fJrXuKFQ#uaUzUCWj%}$pz$g05t#ar{-1o z#ZYh6o&A&s>>NA5>#m&gf?X>M)bj>Q7YY}AR8nPC<0CJ`QolY!M*@PhNF4%4$5nFf z4{VxA-;8{~$A&>%Yo@~y4|O}IqYemSgP7Sy?d}}+e`ng%{?_hDUhCm`I`hP=rda|n zVWx~(i&}Q|fj^k+l$Y30zv6ME&AX7HTjy~frLaX)QgCMmQq3_qKEcRyY7nk_fa}Z$ ztrwMjNeJ|A@3=y7o^6LMBj@LkTyHm7pK(Vxq%M=uXr;M7{wWsrG~I1ki5OQ6#92Ih%Quj|8Z|qUzyy6 zUf%s*-I*73e%AX}cTI5r+ZsgVR1jr6I*hnu%*rSWqzs(T0KD7A4U}76 z)lH{eBF=pRy0q*o<*iM4@ojv65`y{#TKm=!5+7PwC>z)to^he4BI9`z60IYcFC8XC zZ<65C;OV<=0*{u4*i@nn?J4m6_p_jauY-;RSof^%yxer|uPQvyzOCP1x_-}6H;)~6 zkQH$^6A(lu&B^q)5vwSypjGu5P`Y#UdzM%Uhuh>vlisoS7c?a}|1hah-vo_i`e5;! z93hb``au;ow+t;(wB3-=ww(pgb`ZrEODvFvfEiQvXaSX6+A0ooWdEx3u-oBf9V((3iwRO z7r|AqsNjl$(oTUVvOf^E%G%WX=xJnm>@^c!%RBGy7j<>%w26$G5`?s89=$6leu-z; zm&YocPl2@2EDw6AVuSU&r>cR{&34@7`cLYzqnX)TU_5wibwZ+NC5dMyxz3f!>0(Y zJDdZUg*VS5udu>$bd~P>Zq^r)bO{ndzlaMiO5{7vEWb3Jf#FOpb7ZDmmnP?5x?`TX z@_zlHn)+{T;BtNeJ1Kdp2+u!?dDx4`{9omcB_-%HYs2n5W-t74WV76()dbBN+P)HN zEpCJy82#5rQM+vTjIbX*7<~F)AB_%L*_LL*fW-7b@ATWT1AoUpajnr9aJ19 zmY}jSdf+bZ;V~9%$rJ-wJ3!DTQ3``rU@M~E-kH$kdWfBiS8QL&(56OM&g*O73qNi( zRjq8{%`~n?-iv!fKL>JDO7S4!aujA}t+u6;A0sxCv_hy~Y2Pbe53I*A1qHMYgSCj0z6O zJ!z}o>nI#-@4ZvRP|M!GqkTNYb7Y)$DPWBF3NCjNU-395FoDOuM6T+OSEwNQn3C`D z-I}Tw$^1)2!XX+o@sZp^B4*!UJ=|lZi63u~M4Q%rQE`2}*SW$b)?||O1ay`#&Xjc! z0RB3AaS%X&szV$SLIsGT@24^$5Z8p%ECKsnE92`h{xp^i(i3o%;W{mjAQmWf(6O8A zf7uXY$J^4o{w}0hV)1am8s1awoz0g%hOx4-7 zx8o@8k%dNJ(lA#*fC+}@0ENA#RLfdZB|fY9dXBb;(hk%{m~8J)QQ7CO5zQ4|)Jo4g z67cMld~VvYe6F!2OjfYz?+gy}S~<7gU@;?FfiET@6~z&q*ec+5vd;KI!tU4``&reW zL3}KkDT;2%n{ph5*uxMj0bNmy2YRohzP+3!P=Z6JA*Crjvb+#p4RTQ=sJAbk@>dP^ zV+h!#Ct4IB`es)P;U!P5lzZCHBH#Q(kD*pgWrlx&qj1p`4KY(+c*Kf7$j5nW^lOB#@PafVap`&1;j9^+4;EDO%G9G4gK zBzrL7D#M1;*$YefD2I-+LH{qgzvY8#|K=-X`LN578mTYqDhU}$>9W&VOs z*wW$@o?Vfqr4R0v4Yo_zlb?HKOFS zU@WY7^A8Y{P)qU9gAz52zB8JHL`Ef!)aK7P)8dct2GxC*y2eQV4gSRoLzW*ovb>hR zb0w+7w?v6Q5x1@S@t%$TP0Wiu2czDS*s8^HFl3HOkm{zwCL7#4wWP6AyUGp_WB8t8 zon>`pPm(j}2I7<SUzI=fltEbSR`iSoE1*F3pH4`ax^yEo<-pi;Os;iXcNrWfCGP^Jmp935cN;!T8bve@Qljm z>3ySDAULgN1!F~X7`sAjokd_;kBL99gBC2yjO+ zEqO##8mjsq`|9xpkae&q&F=J#A}#1%b%i3jK-lptc_O$uVki1KJ?Y=ulf*D$sa)HC z=vNki?1aP~%#31<#s+6US0>wX5}nI zhec(KhqxFhhq%8hS?5p|OZ02EJsNPTf!r5KKQB>C#3||j4cr3JZ%iiKUXDCHr!!{g z=xPxc@U28V8&DpX-UCYz*k~2e)q?lRg<{o%1r;+U)q^{v&abJ9&nc6a32ft(Yk}`j ztiQP@yEKf@Nu3F;yo9O})Roh9P08j7@%ftn7U1y;`mard4+5 zB62wpg$Py_YvQ!PE2HpuC}3el-F3g{*&a z3q{eLy6Xz|F+aMrn8R8IW2NZu{tgsyc(>*TdV79@?V$jG(O+Iz2rnDBc|1cK8gR$Y zthvVTI;(eYhOdjapHe=9KI`|2i;{VIfvnR6`qof=4a=(BTZkev78+6GJW**Z!|yvS zes)T%U573C~Hm`&XJzE=2t7tFIZM`!^r^&z;W?dOj-N+a10^>wV(l~2naa?s; zTxU{z;Go|Ve!vUjUrZ$B#mWH)NSdxi;dWa-@w)-$wBOpo`DEG<;C#W||W}&@z>C`*j9V|`ai)z*2PG`TZt6T{a zj!#m3`Vz5R9wJkNMsJ1`fSCS2mHnizWDT!G0Ukp$%*_^X1=k=%mmO$^_0_d|kc8ek4_DZwomL(>GGtfEB)Wy&cfZ@9-T|hAq&fx;XR$$_yl6iogcR{u zm9g)axS6=_IL4=wQXf|EkzO68$Ms4*JXAt8gFxLCibt^C#C|I|v|U{%A;+NaBX-Yn z`HAmP*x5Ux@@Wkpxest$F~K8v0wlb9$3gHoPU(RMt+!BfjH?`8>KMK|!{28+fAk%6 zWdfyaD;Dr~`aJHn0}HIf^Y9*keGvm6!t?o%;je)wm`Dm$fN?YtdPI7S=Y23+15L{J zr;n3MYg`<50nW^`BM$&M(+PQ7@p7Lvn(kE`cmoNS7UkQmfvXQBs_unhdfM){k`Ho! zHL0#a6}Uzs=(bu;jnBAu>}%LzU3+{sDa6~)q_|pW1~*Is5J(~!lWvX(NpK_$=3Rbn zej|)%uR0imC;D5qF7p}kdg(-e{8#o!D_}?Fa<&{!5#8^b(dQl40ES%O_S(k8Z$?Hs z;~ee=^2*5S#A*gzEJgBkXyn*|;BBH97OOmvaZ>&U&RfU0P(?jgLPyFzybR2)7wG`d zkkwi) zJ^sn7D-;I;%VS+>JLjS6a2bmmL^z^IZTokqBEWpG=9{ zZ@<^lIYqt3hPZgAFLVv6uGt}XhW&^JN!ZUQ|IO5fq;G|b|H@nr{(q!`hDI8ss7%C$ zL2}q02v(8fb2+LAD>BvnEL8L(UXN0um^QCuG@s}4!hCn@Pqn>MNXS;$oza~}dDz>J zx3WkVLJ22a;m4TGOz)iZO;Era%n#Tl)2s7~3%B<{6mR!X`g^oa>z#8i)szD%MBe?uxDud2It3SKV>?7XSimsnk#5p|TaeZ7of*wH>E{djABdP7#qXq- z7iLK+F>>2{EYrg>)K^JAP;>L@gIShuGpaElqp)%cGY2UGfX1E;7jaP6|2dI@cYG%4 zr`K1dRDGg3CuY~h+s&b2*C>xNR_n>ftWSwQDO(V&fXn=Iz`58^tosmz)h73w%~rVOFitWa9sSsrnbp|iY8z20EdnnHIxEX6||k-KWaxqmyo?2Yd?Cu$q4)Qn8~hf0=Lw#TAuOs(*CwL085Qn9qZxg=)ntN*hVHrYCF3cuI2CJk7zS2a%yTNifAL{2M>vhQxo?2 zfu8%hd1$q{Sf0+SPq8pOTIzC&9%Ju9Rc1U9&yjGazlHEDaxY|nnS7rATYCW_NA&U? zN!7-zF#DXu0}k4pjN05yu#>x8o#Jx7|Fk=%OR((ti%UVKWQNH>+JhH#ziW1hD=rk* zD#1j?WuGxd-8VqG@n_Lqj^i=VBOg@GLePo0oHX9P*e7qBzIs1lzyp;}L3tP1 zl5;OiHG&-flQ;rYznH%~hz>fuJ!n*H#O)3NM3`3Z9H|VFfS-_xHRCuLjoIS9wT!F0 zJ-kV3w>7EguDzoBPxW>Rra0#+Y?;Woi7qJ1kpxTad?O?^=1cG@GeNtRZRi8_l-1CS z`(#oF<;VYR(l(gHIYH$y2=rj5m3QL{HQgbW9O!TU*jGj!bFazIL?MYnJEvELf}=I5 zTA6EhkHVTa0U#laMQ6!wT;4Tm4_gN$lp?l~w37UJeMInp}P>2%3b^Pv_E1wcwh zI$`G-I~h!*k^k!)POFjjRQMq+MiE@Woq$h3Dt8A%*8xj1q#x?x%D+o3`s*)JOj2oD7-R4Z*QKknE3S9x z8yA8NsVl&>T`a;qPP9b7l{gF&2x9t5iVUdV-yOC12zJnqe5#5wx0so2I)@8xb$uPG zNmv=X)TjpHG(H!$6Xp>)*S}r538R99Y{Pofv}pAFlUK;xi{E43^->z1srWR=J$8N! z4jRu;EAiLG9R$5#{gR){5?o^W^!t140^f=vCVSs@vK7#`-fv`P*WV|>nX610pK08< z>r#{r)fR?2pNG}8o)?uvX#UJI)YM5CG@0E8s1lEV`rom|kBmf={%h!o|26a=lNJbX z6gkBS7e{-p$-Vubn$(l_IbwS02j;+6h2Q5F7P?Du2N!r;Ql$M>S7Frf*r3M`!bvWU zbTgl2p}E<*fv?`N8=B71Dk03J=K@EEQ^|GY*NoHaB~(}_ zx`Su{onY@5(Owc#f`!=H`+_#I<0#PTT9kxp4Ig;Y4*Zi>!ehJ3AiGpwSGd<{Q7Ddh z8jZ(NQ*Nsz5Mu_F_~rtIK$YnxRsOcP-XzNZ)r|)zZYfkLFE8jK)LV-oH{?#)EM%gW zV^O7T z0Kmc1`!7m_~ zJl!{Cb80G#fuJa1K3>!bT@5&ww_VSVYIh_R#~;If$43z`T4-@R=a1Px7r@*tdBOTw zj-VzI{klG5NP!tNEo#~KLk(n`6CMgiinc1-i79z$SlM+eaorY!WDll+m6%i+5_6Mc zf#5j#MYBbY)Z#rd21gtgo3y@c(zQVYaIYKI%y2oVzbPWm;IE#Cw$8O$fV}v}S%QDA zkwxW{fa#Goh1O|+=CF3h3DWNw+L^ly?BNQ7DY~Eca}5nt^>p#3cc9s3iDub0nh`Wy z?oH|dW8-HG@d5E@U>NWPjnhTjr7C${Iwj#;F2G@++N=Y2tjV;z57RNgE|kXQC)1h- zx8ODU>kk};J8KiSUx5jSsA_XPou1OH8=R~q9{`r>VnHkU6A=!zNOH8IGJoO!+bQys zDS2-H(7+Jfe+&zf#;OSV=83I|^M;0`Kv*#4%%O7x>@BgGMU*@ajUvY>cYw^`*jm@+ z{LZ2lr{OTMoQXn2XUsK-l72oysi9vgV4Sux^1GsW6zTV;?p#J06EvSVyUq5$f4kq< z{Chq5Z?I%ZW}6&uL+f&0uCW#^LyL!Ac2*QRII5TDGfZ43YpXyS^9%6HBqqog$Sal3 zJjI$J+@}ja9Xp)Bnbk+pi=*ZAHN}8q@g$$g<6_4?ej&Rw)I%w(%jgGlS5dTHN`9(^<}Hg zD$PbZX+X>;$v4NjGJxMDvVBiIam$cP-;h0YqQ{YgxYn-g&!}lHgaG3^B=>Z!D*7tp zu19e;r`u*+@4h41Da&NZv$qy-i6#DdI)EVvmKO*PvIKz-9E5R*k#|`$zJza8QJ)Q{ zf~Vl+I=8oaq)K!lL7Et5ycH;m&LKIvC|z4FH5bo|>#Kg5z+Jy*8Ifai}5A#%@)TgPRaC4f>Qk&} z4WciN&V(T~u^xBgH=iP(#nd;_@L&`7FUF>Qm-;hOljv(!74f&if;fz2Mg=b%^8$^C zna!2I&iCz&9I5ckX-5mVoAwz~)_&b#&k$e+pp=U2q-OjkS@yZ8ly1$2Vh?}yF0={P zPd3O@g{0L=eT-Dm9?imeUP(!As&DJ_D=5lwQ=3)XWXg)12CoB=-g-HX9RSXgL;yo0 z?$7z8Sy9w?DvA^u`Fnl7r_J&_jJ7claq*2l9E~#iJIWAPXuAHfmF3-4YjFYhOXkNJ zVz8BS_4KCUe68n{cPOTTuD<#H&?*|ayPR2-eJ2U0j$#P!>fhd(LXM>b_0^Gm27$;s ze#JTrkdpb*ws{iJ1jprw#ta&Lz6OjSJhJgmwIaVo!K}znCdX>y!=@@V_=VLZlF&@t z!{_emFt$Xar#gSZi_S5Sn#7tBp`eSwPf73&Dsh52J3bXLqWA`QLoVjU35Q3S4%|Zl zR2x4wGu^K--%q2y=+yDfT*Ktnh#24Sm86n`1p@vJRT|!$B3zs6OWxGN9<}T-XX>1; zxAt4#T(-D3XwskNhJZ6Gvd?3raBu$`W+c(+$2E{_E_;yghgs~U1&XO6$%47BLJF4O zXKZLVTr6kc$Ee0WUBU0cw+uAe!djN=dvD*scic%t)0Jp*1& zhjKqEK+U~w93c<~m_Oh;HX{|zgz=>@(45=Ynh{k#3xlfg!k z>hsq90wPe(!NljYbnuL6s`Z!wQSL8|(A*@M8K>`nPJ<9Hb^ zB6o?#^9zP>3hp0>JAite*3N?Rm>nJ1Lpq4)eqSe8KM_f(0DB?k8DNN6(3 zU#>-{0}3~vYJ7iIwC?Zbh@aJ8kfIvY%RveZltThMN73#Ew}jOwVw+|vU5u-wMoo9C zO(tv#&5`DOhlzunPV?M~qlM|K74x4cBC_AC?2GNw_-Uv&QtPOj(7L4NtVh$`J%xci zioGVvj5s|GY886)(}g`4WS3_%%PrF(O|s-n&-SdfbssL`!Gi7Hrz_r$IO@*$1fYbQ zgdp6?(IUaNPaH7}0%U|9X8HFonsJRrVwfmf*o1;k0+PwV^i%f7U{LAayu`!x*FmhN za(#a^@Idw9)jN)K!=sFC(G)ZNaYY169*IJ_ouY9>W8tC>S&MEp$+7 zy)NFumpuE>=7T@`j}8pa)MGpJaZoG(Ex3AzzH>gUU^eyWp*N2Fx+9*4k~BU;lQ1PG zj4)_JlelzJ==t*7=n2(}B4^^bqqcKFcJ7yVzbH_CWK?{eXdpKm);4|o{aM=M&`E$=_~PVi2>>L zKTN_x&qA)@ak=v=0Hl5H6~?LOfO@1+fu5(sB|VWID)w?%{m+n#7bLaszEJ#;$HMdt z9qP0gk)hIYvE1!jseA^FGTyK=i4eTPjTL$R;6FywMBZBPlh2ar9!8wlj1sinLF-1g zR5}hLq>pb1|AC-WcF!38e*kFv|9n<$etuB=xE%B=PUs}iVFl>m;BiWUqRIxYh7}L&2w@{SS-t(zUp`wLWAyO=PEE=Ekvn@YS*K@($=i zBkTMaH<&cAk${idNy0KZ8xh}u;eAl*tstdM8DYnM5N;bDa`AB+(8>DqX+mj17R2xBp45UES|H*#GHb_%Nc{xWs7l{0pqmiBIPe@r=X%Y-h<-Ceo;4I>isrw1Hd zZd*VjT`H9gxbf{b3krEKNAaV$k>SzK(gzv}>;byq##WEhzTN^@B4+VJvW>y|U}}AQ z4^Bdz9%QKBWCy+h$I?L@ffl{fLLL41Tx|M+NjjRf(`KjHG4^y=x3l z!!-{*v7_^6MiJOC@C$WV=hz9J^Y^lK9#tzs6}-

Gn4F+B~IivciU9^t0j-Mgao3 zSDF_?f~c=V=QJRSDTG0SibzjML$_?2eqZ;J*7Sv$*0SQ|ck$fX&LMyXFj}UH(!X;; zB_rKmM-taavzEk&gLSiCiBQajx$z%gBZY2MWvC{Hu6xguR`}SPCYt=dRq%rvBj{Fm zC((mn$ribN^qcyB1%X3(k|%E_DUER~AaFfd`ka)HnDr+6$D@YQOxx6KM*(1%3K(cN)g#u>Nj zSe+9sTUSkMGjfMgDtJR@vD1d)`pbSW-0<1e-=u}RsMD+k{l0hwcY_*KZ6iTiEY zvhB)Rb+_>O`_G{!9hoB`cHmH^`y16;w=svR7eT_-3lxcF;^GA1TX?&*pZ^>PO=rAR zf>Bg{MSwttyH_=OVpF`QmjK>AoqcfNU(>W7vLGI)=JN~Wip|HV<;xk6!nw-e%NfZ| zzTG*4uw&~&^A}>E>0cIw_Jv-|Eb%GzDo(dt3%-#DqGwPwTVxB|6EnQ;jGl@ua``AFlDZP;dPLtPI}=%iz-tv8 z0Wsw+|0e=GQ7YrS|6^cT|7SaRiKzV3V^_ao_ zLY3Jnp<0O6yE&KIx6-5V@Xf^n02@G2n5}2Z;SiD4L{RAFnq$Q#yt1)MDoHmEC6mX1 zS^rhw8mZJk9tiETa5*ryrCn&Ev?`7mQWz*vQE!SAF{D@b7IGpKrj^_PC2Cpj!8E{W zvFzy&O4Z-Exr$Z*YH4e|imE`&n<$L-_Bju=Axiik+hBtA4XNDik(G_;6^mQ3bT)Y% z6x=a+LKFZbjyb;`MRk~Dbxyc&L; z8*}!9&j0wewMM#O`c#7HJ|+Gh5%3~W10b6sdmCg3G_v+@H>n*c5H`f+7%{TeSrzt89GYJqm>j-!*dReeu&KHubhzjSy_c~BJcbaFtZWAB}~KP3%*u{zHi zVSUi2H8EsuSb3l7_T1hP!$xTtb{3|ZZNAJ{&Ko;#>^^43b7`eE;`87q81Jp;dZfC< z$BD`h-*j=%uTpG8Me6dF zrH%)Bw-a0}S41ILo*k2zn6P@?USXtC>pX*tzce7A^JD7^^p7K5kh-HO&2haDTL%2^ zSWQb2B6}e*;x?eKq?CdG7F=wHVY)Lb(kQu1R#1Fx|3?>_%cjNM-xJlAg9kr`!>&;E zTYmHhqHh&qbfO`~w3V;BM(q(_Q-5^!esaBI&QbZ^%N-ZDYft#FTS;%{ zKzlSwZIS%zDi#%DMK>`_vmE^krJL5@PmpT2m26Q`O)VRAL>){MN45|7GTk=q^zLpF zjS(Os=`#On$XI#$A5ewac9Ma}mDxSu^5{#jHC+24a2GbfBJ&Zn8W= zm=l7VE0g^z$3ikyU#ysh8b-PH(&-yZL$JV-of-ZM@~N^#DbQ3Ltlq*5@>WzSNxrRK zYl2VS8r;TT`wLfD_O0dhX9vR#S8rMOuUCRkWZE#OjRi$l*#C7}mgGzZBD%Z=p3z|CaVM$$pyW5-pJJDCToY zO3R5)P(Gnd>6wh9Z$Sr@cMXmClU(h-@5kmiBTNTU-|5vq&Fs!ah|o47kW?SO8uWv> zW$=Ud@@|*9p@Rb=!wl;%>k)kH7fPtcD=gd}^IxN^=Cg>zq^jij!f=1PlT|9jh3K9g zF~Z)B;kb^a0hLmJvON8Ho)foq-oC)&E)b|a^|b}6n!8&AIaousO^VnYzYfuijuEo5 z7IcUMbYD=vec4eZX7;p31NB+T9BOMJp9ZI9$dH1kJsJpEtf@}tL4)_*PxgdOge9_EaR!?wWtBx%*f$IGoR>f3Qf2aT0%+fq=1xVEqRl;UaA2Ncs4B1M1#foI2bj4 znX}t7;-FCLK&;>ZGP}{GxK67$Kz&pO%%J>DBMP_zZsLOmdpDUDp&f8=L>(Kcj+S^jA5dco4-7XN z)h;m#54CEy9)Ch-E7gHP@a@TXl=_%&|iUlIrQzn=LqONBu9FCn`3f8aqvRu=RrJ_RH1^Uf=t z%Ir*({+wEeC??C+u!hCi<5m`RsRO6ti7YaEtY0|U)-QfNsdN{=83K_}m$0Z=ElWyt znvo5=%f<;|hNnL-r#v5ab&S2*yK>~a7m(My$cfd*tff?=?7-j3^|&9H7G*W`)m8M7 zzd0+b)c@`bQN1-^dC$_04tK0{mU5tx_zo;&TWou8F(H_J?O+Y)VLXzmU^> zvL!5+1H?opj`?lAktaOu%N#k4;X;UX5LuO`4UCVO$t+kZBYu`1&6IV@J>0}x1ecuH zlD9U=_lk1TIRMm6DeY2;BJJEE%b0z;UdvH_a3%o)Z^wM&<$zhQpv90@0c+t?W`9kolKUklpX5M&Qw06u=>GPCr5Imvh*% zfI`tI-eneDRQo?m*zD1i;!B>*z4Xioa_-S=cbv-k_#Wg=)b$0@{SK>Mr!_T?H`S-?j;3$4)ITn$`g;J$^TppD)^pRz#^l?XgZ2CW z3g5G^iF*GZYQ}{B|H-fqh=_>)E~=3y3Zg=i75G5E)*a>R9bn~cNW{h5&P(vQ6!WHv zw1-89smtY~JnCQS(=9zM)6>UAi%G-r^LA9_HF0Vp3%JF2P%+E&^afy61yxnAyU;Z{ z$~H5X6?sMoUuOT_tU7i5i%5HI{^@#Hx@zhtP55>r_<3LwusK*SC#%i+gn&iRg z_8UN=rLVp*gT(K~{0X0f_=?~bBbfB`=XrTFn3U!)9n*@Uj$-mr^9PNi<22UJKAK&D z|1@Ck3(Ub;>68;)gIn_Zu{uoVRMhAkIqgBS(v2b2{gf?0xd(1sJfY`56mVy>~^w!wmX_kjW8#?_Nk{}zB9ULo>4fO(vnWfC+pG4>%*KZ?JuCdXu%aZ}q7pC%E50@U9+KQZL5 z!*I`SOtNf$Y$CsRsNaf~yyw^>#X_mCiF&*gr=cBb zoPu7PwX(+Wvl~i(XH|)jj@Cu+rzpJMn4kVvCJ~ReCf08viF$q9;CYnv-96k{G?pf_ zQglN`JiS#vok)~^Z2>41#7LPFgd_xrqNO%DQI|!Qs|nWt`co#BwY$&Wm^6#~)`_1k zpwiR~&z#mtSDuYm(=NoLv$%Y}bTjog$RJ8$j1(s})=}su0b?o8i28-|xu58ipFBml z2`4qZ$BbY5>(i2%wmh!+C}$97?X3LgTQ_{(SaFZvq9YCn@BNz z&h#;4h?5#`&_0()uJ;_rR(Q^eY*=&vu)#EeMeaN1puPv5+iQFg1EC(`_99_5v<1r4D ztc(+-eVWf_np;q$M*H49#{R)eIWCI%R&6F34;h9eNG(XNO5ao2MI8;j}y% zZeA>zX{#$;muhtY{_|;bkk~!U~Ih z2QUO}hk~o?sn;#|Mt$0}4=+BRa703n6>fBm(cesk8Cmugg_wi|BWj}V-VuU9jNH+o zgNYGSKPm>qR&nI(2Gu*})AOBfXf0J~CC50C!3KXu6-qZAG!VMZbmnqL6HWG>o$^sjoSLbQxra@WyKV$+_Qe}t7d)c`bpJG++ zw|9D3>XUH^Wplo~MN%WK18n3HeXoe*jKwVRK!=RMtIr1v z;Py~7;eZl&=^UyumN&CecrGBEat}4?mtZ>@`wPjVK@Z)FZ;05^9kztq;qmbxQIJ4kXTk)) zaVfD^K2x7SB6E!Zz@0p|Fkge*0(0?ogmTX8d=?n{2x)}K2$`bjDmcLg3#wU)i)by? zW^G8rRQKBwjke5zHScinRlE|wo0XyhBc9R52IsKWf4-@=l!yO&+l=K`-7Ib9U~hPy z!cH>H)e6$;m&w^0d`axGqDwBgu`B+L4a`xr#5g%b=0?c41`|lx0O9fiIVaFAsO$Ol zayhm4C9X%hzUf&ctylV$%ntuA$(yo*X`gaVX0$|x{#!YK^cvLmNWPZaTd3&xP7ny% zkn}2AdJkpAgmsh}Q$tY3(2RtO;%R*~8r#ZbSbMR4LaL9Sb6O&Ce(GlO${jtl&`n|D z9;zUQPXCHqTm&t^lk9RlZiiquSY_og^?kgVruz%myd95Fr!V z-$OIXSt?(pxN-M{NjA)j1KKIp(&c2RVjd_}7+CbQfw zTRjg}A0~}Ht_?-@wD0bI-;LQwT?mKywmDZ7*j4>4pR6@UVU3mb?-cbQt~aIG&RBjl zs-4UNtOH3+dAF%U=={qB@qijh4J6K?Et zPLlfPlv<+i>ty5rh;Q>iGFoaq4LyBIZl3L{KGUmqPL~ZCosOl;7w2SxcE}pvK;5|6 zly3JjUsvk|d7L3bFs&;q@_|p?vdU_UzhrS$Fw-_NoEdoIT#-0hKC37!>-i6FaO(es zY97)m4YO<|eqGMrYejC&-IFmc{=P7>qFWX;)}q!&e9-F59o>V+`X>J}%Te0$|A>0W z;7*>m4>udzwr$(C?TzhZqi<~6wv&x*+qP}v?C<}aI_Jeq*K|$4>AGurZe5=U>-0IX z>&2?v81(_Tn1tITYDSF@^Enhl9>e1$iAnX!+&YJVi>1uYEWsZ?o*Vyg+K~%XCxQP(WrdtEpc3sgbpTM_ zI7i6|pDr z{=xGh4O=PrB}pkX@o@A(%GfdU!c<$p#T*mLo^*7@bd4rIJ5eS&&A9VB$EhabJ1^TG z+dke8lOG5I(xMYZ`Xw8+olY0y6M)M0rcr%9tZHa=G0zICN@DQ>0rVASCK4=3OeMSv zD!v+POT0`UZEnP~1ro1?HPLqJ)xx0#Pg^yBJz@S6gmFN~cGvl(#fz4oTs7_Pi^+i_ zZP7<#ukx>i%V;uJJ~WwUW7pgq=>yuT+A5w(J5$1no67e(;mIO5>@`(U0{}+kg)B_8 zs=bfBbmZ{U`xjMpkAcEcEeF7^#ka}2zDU-sBt6yQqw&2p<+6Hb(Hi56S!+bU9AJJv*{ep2vD zG;PVwX@NC)+=6@I6J=nW6_99&4R00FKpUPepXoBVN*|V*C{e7X+Q({6O_^@SlI(9Y z8kRO3WDG5u=vmTjZ4DW89H&vNa;i%H@`{%(|J%tVs;1gDadzF0Jy%}C68|k?Zr!B9 z*lBN4{#6p#SQS-q#Ck&x#xhAOu4mK=Jxf+5E$h8l3-F4mQY^qaS5;Z* z-ddglOueLtXJhJ!%yJGk^-iZ_+qLJ zpTZn+6kq81D@^m(v$VFFI1Q!dtczYBt1xSn9~Q=@h%tsf*hCm%fwfx2u(u=-4|qf=I8WR*%`lsQ ziP!-b?(d_`TdA=^<$@(2c77&FowB0vhswM)fS>lYvjK7B_$<0SiQNzL6T?D721Y*( z9nG=@aWvmJMd%j$Jxp3-L4x99-X-9aGkW}yiPAo*9{^6b1>tDg4zIPFiTqVK$xq1rv1*kaE|~T5-jH#8{g31#^7M_uSsmQvNjyk; zbo|yP0w|uD1)wGrSavi=<;=H>IejRQlac$HMkU2rbq1{8UntI;oJ}*o(bXy{JC*l&^W{Y^}<%Nj1Tk z$(9f2a`BoyZZqxWF=hhmc3ldg+8&Ep%fVCSjopduonggw7@?XulP^JPo+_le`o@z)ofi9U%I z=~YZ3?Jok#3NeQ)U&qUqvoyuEMA?b&Ki=s%;_MTDX+8^>z@TOxb3qw~biG4!)XuQp z=>cVLGcp<{Piu-TqWLFz^P0>R1go1M41xFSn~y%8LZ{~t{iz!z$|ne5qkw!VwuI<6 z*6Bsnap!L>JA;B$u$J09!L&_iGdX<&v1jeDcEWM4&2q97^g9gK1%+zl7nY)PUU9<~ z!B??-0oFH5TEpfNW#V1m;(6-=mlUxm699O$g=ZrFZpn(6h%3n#!U7eFnC1BJzLFB) z-)SER^cpQ~AF(`0^?pNYWsz6(suJg4)Ke+|iTo4!8P8ND$ML1a%4|QMYe@SDDH#d& z)P6SOk~%xdQ?i^t{N0)(baSgQ(Fp*daGXR>=Vt-*#@)>A1Sfz0!iqKtjlY4}1i0v0 zyz)Z|vB+_QIX99Q+NFppI1+3`=qUen8NVELr!SOS8Vq1;{<}WKOhe7HMurM4mg~j5 z%|wM0)r4^=uC{9_OTf*An{G}>6hw}C=H|&8MY~l@u zmW-R8h;dJxjKNqEdGf85(5BrR>lY2A= z-_%9;IglQfHBuO%U)bt|g%1h-OMbL9H{TdFgM^rdBTt~gJ%{*c<;b$D13(ac>}*nJ zo@&y3%13-hUh^Oa$9U1ImdNfGO4bPX$I!c!6e;sRC>z{knTf~G5{#4J7y(vbrq-qWk%J5#0Iv((P!QKa6f#3?;#q$+(teR!nw%kOp&_W`3L^Xw}Dw&e2#l zc{fk56;UyHDpT@XdB?u!*)EdIMT8X1&e>VO;M_QH&MXI5|3xTbET#NTfyi14#+0+t zDS(NC?jbc{yIDjm-=9g^4*f1c;0!ytb~iQ;DSTKoa4ow@d-x3HI`EYcAe(li zjajb0cM*@u*kiU{)jd9yTNeRZLL+Y1&q`L>gx^Jj_B%sh2+%Z1d6xNVmTw5Fw!kd@ z+uT`4r(0=PXUZCNn9$VPo=aj+p${a|eqjB{Mf+k&$GEGV(lWHl#1xy1%5E)1KD$bK z0Z1Tsk4LpTn+b-iy}25uN>wvTfN+B~4r!aC19d7}&hDFchbqZ0;e7I0BK}RNujj9n zY8As>D%ez?Fkng~c1L3e^}<%h%!NhB5ZFmv4qmi`am*+A28lE6Pu4ekBJ8DW?YR4c zPeG`sZYLihHq~K3`oYvnQL$26Ojwnj1AOypgX_ca^06&6f`T8bedVhWj1y>F>d-sg zr9@SeL^T`CHIwyKW*F#~AZd==$aA_zOLRP>>S_&HK0s{HcEDpNQm9u|IZ{W%#*w4} zmN;)dX5OA?I{M$KLje0TCiQd&|g9E!YKD5 z)_8>@<$&L)EoO;WhhvUYgEDDJ8PPVpR_u`RN${}`PnjHc-4^~CwIh;mLF+#KK>Wc> zE|Wkj(OZ@zIa8-8rUq=a=x-F%J+$ozWaVUV@yS!{UWJ)}=^jM1_f&XffEjCb6H?Es zrqQ!sdrLtEHq=DIu@B|%&N$@{wC|>I`>>2EXn@+22x7PaM4p3V5XhXp8gSH8{)yq+VsXB@4DmPLA`4Qc`r2Z>3E&lVsUbpRejKO8Xc|ayAI6YT)d!q zrfQj!sa@T&5KPMxDUd4bZwub#5<;yenI>0~Zx=@R*M{S6d|Z3TAEsEW-w#undSQP7 z0ryg{By3CNOC^`$t=P&xCf<~vRz1}|>Oh+v>rBMi?&+;xKSGs;7Ie~^T>J4C9Ke&G zL&{aTYZk-|Pa*unK});DaF?Y=y73~NA0(lMPUz1G>G;8n^cmm2S>twrpU6ynN~J1! zHD!AXWk^D?nq)%#A^&d%DwIkh3Ku$<4{$Bnqe{R^e!E zD6qaK4g^V5kCJH~Ot$Im{2T}8sS28Gk(>QFg9I7A-=nDns|{X8NjAD%l(zhXxPR+i zsaKZiVQjKRN#@N{`Cm?#slb!NghtaUv~`T@mvslIbq5TcS-15muB2Hb$Zs``b(Pmm z>-keg*068f|SD zm-1~aS@!4?{PuWQ(%MlB?$oG~Y0UBQX_Nz{MC3%JvnoK+x5+GR`cIfTOE7r3_Xi|f z(1x{Bqg$A^m57WLbkEAc&hWkBABmV|cqNS(`o`}NaSI8Lm6{l$b%3paaK-^r1yrc* zQM|lY+je@P=AS7fX6VXPV>UYV77X|5G z5Zow(9=j+q0*H%#H}fpu-HF%`(GEbvHmWK({pqfv^b!p^KiWxjYXL)gZO^yLvY!1#{eH$?|l`7XcETF-V>)m#$Y-KUauf z^b+<*r?&Mks6o?n2JrEvgk?j+9|~S~2U~dq^}6M%or)_T?%jaFi!#+q3>YaIG?m3X z;{>&cQSHf29MCWgsDR$xyTZCe^~uYQ{iM+(@1tKCpyDxFoeVGQeW)9uT349)IDK!3 zsmbQfykCr7P5@r7$@N8b6KjN-vAfM%rz7|bveQ2v`Y|)B{2rfRwNw!r&1%%b*lWIy z+l$A~f%;yYgfY6h_(-1nXB!C4(VAsEqS^YKh9a{{_uW8t$M^?gPsm-J}^#E z_uO7hC+?sb1Iw^TeS$QC`8qwrX85eSYLIFX93I>dS^)6QIMdwX$;6F>2_T&M6o;jL zp&W3|Bd8rLlV}iSVY9G7Lo?V2_E`JVM(`rw^}DX9)wk0Q5GJ%esB@}u@C>dZ-byh| zBFz*MoXGGiF}DG?h!UZ#FN`;~1bd*pAWflMa5AtD-+Ut8Ymf#=b`potx5YLf&A%ZwGv$|Si7 z(0)Re$(F;{=Dhtq1%wCl0ijfk+T4jd3}^2Z$Q?L=1_lkM&nIax-Yo%VqZk6#Et%n& z0S9_V?yja0r@wi$m!-JJM2G=aQ@nYectR_Ln*dN6gmAR8L^dIf-bxR>0A)c$?#Ug@ zVlrY8#6Wp4wiP3OZ1@T=EBaaz(jrxuLG%?*J+=c#K7CorpL5*eKWVYiw<>#a7zv(N zO^RpkPM=xn!2?&s^7NCTu~a+aiGwc^_4Rnyqj!-l3-f+;6mkOx5@ynO(YF&u{yH5a z0{{W^{1E}V-LFeZcLzkH=SpZ_y1l&>1S=X`+@!Ai#KmNT?5ox%_;tp9`=F^;&%fxn zpX4I|M!d6`y%-8hequbo4%INVKruc+o|NwhsZB0<&TBCe}v2@CyI^$jlCsTrwmBFnzIMofx8PeKa1Av-Nj zlLtw2SI?rq_1(xc%<3sF%)ZrYIf>Xe7@jPt9BWoU%bg~g+6=1f;eW00nOrbo#*(mjYHCr_?8!#my~|i(0+2j{Uo+J%%rvg+%X5* z4!HCVyg~`t!LBG+X&89L&@QkGXe};GQ^moDsqI%U>#?IVQc53nUukdN%ij?m+%#Fv z*$`n_GFdWHC(!1z-ZhRjEV&n1wt#7VUXkgkW9Q5V;)k`XOO{*>9)xi@4}6zxlm4Ck zPC4Eq^0qB+yLg@{^VCgieuns3B!x#NzSr6q_VlhP>I4gzH4BI}DTx^r5(>Dyhc;-w znWU^i-9$N49%O1eIWyBV{K>wROpYjgCc5b?os*f=l~V;o)CB3G-E7LA7Rg3;!)~m@8(whM7Es zwF%4mEd^gMI<<|N60&DB)!+6-+8@EFbvGs4UP0$q5NEO<7?$NeaVcvz#eXkrXV;$H zPjNrI8gWTpphtwY&md>1N7T|$T^i@CM$EWZ;`6{q__Yr(^B!<>OPXT5%ICC%;4jl=T77^3T z0A$3`@j>`8*wH>vT`en;tj&YA60zbZw2F#^jE;rfTJ}-rcajHddN|Q>g}o$TX~osy`RPP=q0j_f1g@QgXPlY@q1Jh?-r4bB@~25Cj@AmJph{QR^Ya<4r(z*{F~ z=-nsVQY2K`sKEl*CR=AMEDIZD88T(wtjZ_((xf$>SIA*D#|jjfGw84wta;Nk03w~g zI(#i!OQDMse#AO065D@_gm?pQx@{rBjMat|bA$6MfVPq;S5zT5IKK&|LFZXuA zqj(kJK8jP}^ZYm?74hlPtf)m?w!rUP42d;f3Xx1K3raV-*P;*>hmzjAkyfcbEfZVM zJuLMoUQ0*&6p_BS@>f9!k`6HtNO_~}(0Jkg|_f8#- z!m%Jn^dX^G#qp$LnY0H)6WbFMeDL2eCjALoKs@6Ai81!~l3d5bNgZQ?f zTgufN#)|A&im|)K13cIGc?~(RCQ+E^pAR%xa6I`LxD$=mcOf z@v4=zb!i^TVJ(CsX?zlhk2fs((qe>+8Y#o60peO430M?7HT|g( zcVfD7@Ob>SyV%mu6}7g*=p&J}hJTo9hFn2o9Jy}QCXfAbC}WgpkeMXs7QNle)Z`PI zaU4~Uz`idIpQPmpq$?{N(5Wj_y%UX!5{=9|{BFV$P&Z}ciIVj<`zLyWb*T2wf|8o* zOk|-Qs_aJayia$?0k_jr6b#)1ONJ!Z;{~4NDyZJ6id*&SjT|kFCPH^!Q8MlaAE-*_ zNR!vqG}YZ6i}M3h>ENPmCHxC(#1( z7}2c0*RmVw1@+)M+n8t~gQT#+Yg3>|OA<9`Ynl5)ftY4g0EGA!t?E*;j*jRcB>mr~ z4f=etCrR1X;V_euWY<6p_AK%IoHB+bS8vl&LZ-5Q*QvzmfHq zZ>>MgWVvSa-wRV7cJ8O%vi&R+@2I&X=r`1P1;x8lhOpY4Z58^@Wm+--yBQ{&>GOL- zIJm(euOw?WYjBR|f~ue4(%k0i{lp`gI1~mF;g{;-0_gdf@ z*Q?M9wQ1ZdZwvrK|IY39={n^R^(zI|p=Px@ff|e_NEBug4N0vK!L9-J_DIiI7e5Pr z^Sce&Prjs*$mOY7Rf3V+?poBWP^ki{PIa+)OK%4)E`rV zxx7V^Qy14sZ;Dc2jD|ccyt5(5Zp~;Rg7N_IwB&EZ1jv&GoxT!1H7k>pY>Aa{$&oHg z`ykhr&GpvCL?|Xb;O}(ErzQAl=DZgICR);;Y=xkO<~chKzvaND<3}Wy~d>W0L>Q| z2-}wM73&w!hC@XZojB#$EnGzb4HAp3FWovUq|4f%x4KLKUg6YfVpokO|+JO^JSzIZEji>8`uBI~^1wYq9L`S;8*pu)y zTN!cO5)p_vO7vsEgglr#ee5WTiRh}7f0zLYNA)eB;_ z63%8_pGF-Dnkx@eu`dPn7Z1~vMk@*nIMW6HtpQX86HiyI1H>8W+4Y50C=@;!{F)Za-A9+#^G9aiAu<-#DuLR>+Vm6|21n$W?isfhl9KnurA)AcxJ* zIl$Iy_sl)Ewu1nV)Wiqc6M8RZ-OvG~x&%#S9h{L)QE&q|7$gk|*5h2|^bAvwHm@~P zRY4`*Kw4vB$#(Yqt2+Rd{vNGl*GA$FksiM6%fjfp!BEgA!3EEIq!j+(-cS%{(44@I z+KuDSMAy-fyJ3j}-3vV|_^?zVAkrrzw!3@QF<9e~z*m55Kjm<#D3z(4wCoyq=E3Z+5+o%*c82=9Dn;-mR<5ukCVG}$pfS0a zGXdRdAa-u4>?Cv7*|^+XrkWQGzzvT;h$l5u$vMI>9ouxPD^S{5-qvWAprQ>*&?#SpxdJ-SE&Kk2hn zy8lWI>IKrj;hSj%<-bXl8V%B!q_?jcj{k-hy&J%P3vb%^Qfyv08YOw$Qv~F2IOcFi z%I^ScI`VdU!El-&Werf%8X2asF7Tsk7{xt!qlOL$mCejuXC38O9pJ8y|M>$P50HUy zhcG}uKWP7NB@OTY;fq3kG@GPwLy>1x#YEu`vmQ=(0K)g*ckkeaAkM(C2nZ)rJS}8_IMTxIBXH|>190=4 zD%!`?a-E!T;jSVXMP%ETk{4ij&~`Q)&DZieRx)rLfXGfwvm9#PvZgMyX7+TpsoXa= z4Qq583C|0#1W{@tX6kUwtN40v^oyycsiqPP<(V!5f5bA~B0ZGZ{CU#4q>RznC|I_) z7I8BytRK$$wnfi79s*Phn%|0s_u9`zwWi2#=GE5F_sk({H`bq&(QCDy^X97O7~dVV zjm7hN0FhFY>Zr6d?l;%A(Z~&Ew$4)I4_&92>1%LB&Iz>(85AY z;VB`o-(qZZj2^wUL9TY=pDZ9{|L{Rg0eiHZxKR(>6I;B}xV?kpOG_~18o5kM9>bF; zvl22sk@FP)d1Mu!iPBd8n%hqPUH?B{lf+vBfKDaUjH};FB`hI|=TD}i4-Df(W|+FB zCt09JV@dNOy}=s3AS(U4&Ca^LI#IkDbY6-0Iby5ba=y`Wp2hYzhwTE5+|7W}HwTbp z9OzNwQYpe;mIt%rDX*W89h~mxYK3jmf-7Q*)B9kUP?Evo3sn(X81NyML>*eVx+RUlBPA+sDViBwk z7*Dl;#i5JP1+7=3^WriySJy*Ub#&|n!0jaOtW}%-grYW2t+eT{wz)iu1P?+?*78D4 z?m5`fN!6Uv7J4JU)^8tW`D-N9QO%RdtYTA8+bXhEgPf34?k{g{4Tq?|%C$Kz+U{9j z8RcUt*R}dKX*G74+BGaNebZUV{DCm;@U(5XnJYWyX(1gNvxR#br(Qa6)^hmsfX#aR zk+}yFE?Rp5@=+8!0rVoYMrk4eHt6+-pV!|CZFOXL81z;&nOQ!ct!B%hYyCe z$8CC^HadwLAC?`$JgYtvu%$b7`9Y=%pqA!R6Z96z- zLhL(4qE89OG&)oMjo05P>;5?Mp60` zPWdJ5-2@SE9T{-ytDRE{6sX)|Y1X;+C@K>yY^}14Y!088xh~SPfbJG?M1tBi?E>u?zdU>G{5+S>|$%tGJB zQ*X_vOy)g;@fbPm0a(Zh7zTzw2Ct$FB6Gz7!tmK*tZ2h588F#jY1p`jSJMli*7u-; z3tSU(fscAw1h}5i`&i`+?4UAF;AeV|b}3)i5zA^E*L0X|u;#%xYNx~?#g6jEh~;8t zQ8$5Sx)(-Y-j-9ugVW%b2(t*(k6(`>S>s9^t-podjkrgd0G}k7#${=(J0T7``%9)` zbz@# z89pMA4}>(ymEcPbh@I>#D9Az~sbv{(OXEh+fnx{b z6H8ULM@UCCdJbtvxLPl+w?prh49<(wWQ*(&g-1S%fFdrWy;&bp2wdG!zXt0n@O|(h^&64U7Am>%tK&1tn{(CN?9?pRJVbV0abQse6W* zjaunJ1r9_dkDSXE8y~{blX@E9+XdZr?+Cj9fSv4Dr%sM0X8+%}yVNrc%}Pks zfLfd-a~NL@9Ae&`->H9ihbrSTQK7`l0(9ei<9)-C-ZjdIKdOKOVrZbL^1x5+({hmz z^ka^IzOo7Z5kDX{UB^aJa=ZJ664{}im=U8r5}V}6e33gr#%&kPksN&;R!|y`-hx0+!ub!fTfgoWJ@3*jQ48CTp{?Y z$+bKR>!aBjD7x?Y0>>e`M#1*rfv0;edmByS@dJq0U>!j z12B#0J8%)E#AT3Tv<7hwsa2De$TgZ!6ya*gBbt8{dMpCoYg`{48qN!f$4KFI>9kSj zXqP7qQXV6DfRu{Jr(Mj>;=zUW>U{0sd8$z^(2$UE1b=z(K3T=YUsL(r3UwB%vS_@i zUw15;g`ql@wnozVkC>v|rqdrPO1t2>x^$SM@_>ucDEgntIq=60A2|p%szF-JmH5_! z>2S4sVX}c!H;5b!MnOy^fZYTP60VDhA{ikCTh{$>P4GK|N)1u_VGJ22k_IyXwj7Sj zcn5~M5{rQqE`|I<$3Bj`K#{b$K^z(UVwE$D46wB&kBgN&?rjSskPyQ3X&G^Acx^iv zW6lXF-}{o%ux^olbi{%ZmZM_C=6u(%CKQ={xs{jYqD zM26k$`Qj{UlW5Jt`l&1QP|d=7B{Dx;qd$8JdU$AE5&l(!MUkXC0mFRCM3JnDw?zVe z7`mm7)u~!VZs$|ahb9Y>#(9sjOV zcH~0w!lwVVM3oxLQd(|~MDZCpxbXh7qmbj2l;)N4J+?HVc6Jx7LG<@F&tGUvek#38UUOBInuVP22k}b4Ep?bEu^--cB#Ag|hqHNP79!T*v5&|g?2bQG86x5lB{ff(Rjr7|;rT&I0Ef(#dGARy zq-)N|z^0X-fAevH$bL+ip~x^dH#=T?vKN@HF~)7*3?~kd(`GwzGp*%S?H7db>`8F> zgx!tP`bl5-7lQ@AQ4i^?mNUb^ki+(Qvxg{R!^Ut%ya1_K$Ci-wGtO^W+(5We9^Z|i*}v@%bg{vBl7i??boO`xvQUh$k~C|d$i?y7U=W| z!<=;Y;tf9FpB=nOaU(_U#7Npj4id5?8H4? zsL^r@1_p9?VMR4cVe#mEOOH=f?>dB_m{#vzpM&E&KVbxd<&r?NMbz+F*duzV(?Y8LUgUpO4?&3)QPk z5&HoWONJr}EUHfHzJW4vCdqg&<>PN7f)paE#1!i^P<-8JfbLD7%T`A%By{h7P)CAW zJ1E&XBE96%#4a;dwNYQjcdiR0Nxh?uH~|2q&7C9LQ+QSv8X^PP0>Usz*HSS9C0>to ze1pO&s7BCS{x!VW_Pg@E-%TErJGYbnQ2hXL%RBzBNmFecgMmO#_uULhV~c2I)KHP{ zv{Eui!aMjaX?Mf>WoHp0KtGR^e4E^69*4@*{%8^>HwxUFNcSt7W0h7X$VzQ5JTGQg zLpd?yN%(bgiP_o-cst z@QA_VD0&n&*dj?j63J-vndy~X;lwmo=Q_8PV#w^VZOiYw;}mS|B;|u)e#GS8JRqxP zoWEuBMb#F=PknRG3P* z4GJA~MMpEbM%i4(YahXGEOSo2nB;oM z*5&1O`U}@hdRDps0PqD~2c@$6cz7sxmZ+b)O!Nllqto*I#I^<9nQ}0`3gtZjgFSc` zr<;IuXQCn=vP25FV3h8Z+}TdG6Sel7VCP+9#!U`9SHR~u*QtV&Ir;S6Z^sSGm|s;y z-f{CTn7y-&!B@eo#~6{h(77Nh6dHLyQG)b$p_3Gj)aRs!q6N>lUC*~^HSvWstrW}u z*CU=O3^xF*0&%aIQS)f~p!Vfgr70q9_)Pqs1=T}zL2n7bM8o8g#*F|Q%n>{#zGI3aoM5ptgqb|5#Q0-fuPveFm}*t#6J>nQI?04W zddadPl-27!^`1tRpwAVEqlr1diwI*)RCifevrPbt5Gp@fxs&zT5 zsb*ne&_BG~c(7H^P%7ADWn2!iMjp*h2XH3HT6VU72#$t`4=n-ZMCj(Lx2fTA@Q*v3DH1nr6oj-PQmZ9zCOcnn|~y1H8R1_aO#cRLv8n zA^SQ>qnD0V>X0{ZGw#)({*;uB(U$-bb3>y#gPQ0j{V0TAh2!q01pnET-gA>Z&%Zu& z{QmIumszVzi2m>gDlumvArvK|eWjErehNwr_*YQB+{U0n2iH{TJ z;qL1>Q|tNR;tK>w-Y~Xr!pxa~?@n`+EF(yvE$iV|s+c}C9kp5-ApELWNNyD z|D+=Q7PY%KH^%y&U#ewXB(vfZd=y2g6mLmY^!M=zO*K@jEGVFm+gRBYv6`7`j!j#_ z9w|2DzzCJJ^>~J#5j;E8*py74CK@&dIy0mkEqwTPE}}scXFHs_!v+39v(Q!~u%}FWO}FpFHX>#>99{bVQXu z&Mv05icalrL5O4IcpQ-%8V0q0)*4^oV6E1=wCFNkQG8D|Vcl#K3ekLmEmuno2}tcn+QcBWaoDND z?$>_WkP~3jJBVSpFIV5PxKA;nAt-PpDTxDvS|U0B~sCx$DrPuUWy1s-9;QX4FU@5U37&vhcuXyFpWC$dZ2bo2M?j zANK_Zrju>J;S;e;$Q-lXs>AJ;X+V(MnIVQV<}7RvF2tip0dAnk>SJRl?)-~WoU!77 zQ=Tzv)wwG*H6)RHIJxxBSAnc$34YukwX=MWwb+&MO&{6*3?R8{8xnSKM?Fx^SIqyB zbIrq9*-wfEPB-!(hD)U;417Yhr*_v$3yfCOLjgK9ct=m3wC4po@*K`;f?423NQ%Ha z=HQfTdxjl&#yC@aA?gUOwDc`m_JtKN%GtmX{+jhTzM{j)Zz!HLVWS zT3ud61ZuseM>#VB zB1v^H3>~f3ZuQ1y1W{>t-Z=ZAh`cL8Ph>}_y|h?Wg&}{_PP-`L`oK-Ig}U9hdlkA` zD(w7nYK?aP_vu?cAgjvw$DWY~|Nr`6dn+Ike-c>$`F=-2aTLj*LyZCcadEaCUHG~; z86DPAtoK5nu-&tR!-E*UKmtjQ&F-bed^U;yv{`=a-Q3MyR&EFcei`C7LwUEikDKv_ z{n2hUv{KSVf+2Ghr?p6~s8Uo}UNjM-Va{4f?=S0P)GQHiP&5mMDO6_~Oh#6NWhYTD zHVIY-Br?zR-A}*_d1E(u4)4jZiSX;qv}@p<)$5PHa8uof$- zN#h;PX!Sh`GyKY@#3`XavDTF!tlLp7pOnP|n7ydSTSeRN`9lT0{FsiXdyibTb1c%L zVA^GmC!c-pE7zzK?fNiiRLgGuZTzKsr@X+hJ&sngBnxa3+bfw(?G&G3Q%W|MUt{C{~s zF!W;nx?2MjfY!+%*n5u;$!Pee07wYZ@g^V02=j281Q-OI#l0q(9<@WCr<;o4(a|TM zH_t`S9?g&v-JRw*Z;u>5#?|UTBD=ggqWPrGOk$%Eut6-?OV>%E(R=5l*y|X#64&>rZ z#W3LPCfr7TgzQ0(qgidWUQd+uWMCx7o zEB>|%Jj&TVz$-D|qVAVU4!CF!@J}!yxFe4cX8SF|Y-XBWZzD>se-R!+{t?Wh6=}E7 zVI*Eoa1su_6K2`e8XfsS4OJM|U+&-7VS zIRJ0}JFs%}kcBm|$KkOHXW8Yj-C+KS#mq``V56%9am)P^?MzJPWU+*SyoQeWkRCz< zQ&Lq-Q>VTUJh=@7B#nHSC6HUHAey1!j}y>tP-yPh!o;992`-QHd7AI5t9 zPzm;}i0kMO6~Kl4TT`Y-BTU9Ku;r}*Q1TDl8m%S{+PFzk4&HGip;0#LkTx>X5q%>5 zvea2A%tl(PyC6CoWZ>)xHQQMu6n`UxQHJwS^%+zbld7C*CafaNLfh=(7&7eb)>jvC znLDJo2#ICn^BvWW7|$|a>!k)dOwPL;_Ao<@lzuJMoVs>;vkRhel4yyS2) zNMgz=@z?&pdF|R2kYSCb~_c?Vn#f0va))?V7TyrsA4t^o14=CVLW+YJt zornR!@R}SEh5X@8Mecwsv4(I7&TsC{FBAkUqM~hI4`ElK`EdgmwXTtz>9XPZVjTba zBi?BtsK{w&VnIK?b}XqbS5ujgFthngi(n$Qf0!GV*Ck3#A5=c-XwE4I2shGOBSw|T zij+DsI~26%8A9#jM#!kkG4k(|p=DlNOtp$^w;d!`3Z6v)Np-zYDWC&3J{ zwaUiwtA2L~pTeKQ%+q-puz^>p5WizwIVWT}a7;I6vmOl}V!9x!Q0+N)w0dK<>Zy?Q zIMqMK-zUY;#%$)=v;*}7l%0g)L@qrQ%(KKJ+7(26naCnPXDl!4!)l8vCvdPEi@Jw* z|6Y0vPmvHvkk-$$00p5yRzY+{Zx>_nKI_Xh)l_9kFz3dgjETw(U=}g;=}5EaiyMu4 z_K5!H6(p54QnUJxGgc8!K#+;aOOofhNq5c;z10R2IrtP1H4@T9A)rjBp`BPHrYhlL z+@cieQ3~0svr%Pi6*}fPW-L9x=CjjPl73d0y^9szowR56%tm}k>B)RtEMvOL*=5n6 z-O4NJdBneKC@(Ak6105naj(;SX_5pO7!J@7^!qDe`+jzeJ|J9eMX~dq_a4ty_&9?( zEDkVKBj$N0>Ka>58Y|PQq{Q2j-1e%45yo0bM~*k}vj%t;)h4!(={qG%V1_LSFm}aK zY-tE~MG&?}B;H1))pTEj@~LYqj3<1_=`$4^b24-b8Y}Do-qUr>x|NiG?ruc-9+TCz z;?EP^qy0SZdX`9sh!jt2^KgHyRrl?I`X8rO z8NK~qffuwrcv^i<^-sN;(~rF>En&Wk(?xUpXJ1i$BT!_#xy7-)Kt@ezB>Cmr;5qh^mji@urT}VzT*Om+_r%F`x$OqeakZ|EVfr%`L5IZXlLN1Lx$X$ z+~*?=bbBH!DkWE20Z&N_tCU_B5$>9N<-1b_)B4t9h0o5Fdg(TV#T=ZS;k;e9y5Pt( zcf%BKR`r}pq4b=}Y5!VT0!2?uu5S_u400^GsdDb9m9+E0!adTPK5T5=_*&)oy9xJV zF2%9jIC6B{IhfKk_L`{##PdAGvbj`=i^IWZR_QpWl7Pcg=0JJdXRWYv_wxuM9&rzRW2JGR-w|x_nY#<=SNhGv@xPUGak-)N>My zOneaxybJRv4`{BQkx7I>1a{^b!-nmXAIx>-%-v{b>i|3i&3>}pJSUmS2~`n_z^+yS z5F0W84=jO$-F%Y+=gUmi<5!s6KVLxR@N}V>dBECiGq5qIhN93#0IX18zN$3hPIm?d zV-!XFlLO}a%OLKmW?-;Ek-sboG(;JA1H1~@Hsm`!ZBY~!NrDxAkW>XLMBK-SZsJh| zutEn#h>3_B?HCwPO>9vHDV(GNHjo8$f7;~2gO;L~=q~SL-0fWZ~#j)X&6Bqf(AYY$jk0PJ03wGnXMds4rYbk)o%O?X5s6!3k zfXNPvon#Tm&!fx7m@-U0Xlej*iY)lxbYN7j0b(5#t3F$TR4GoDU7{+BI87QonpRme zOct=Q1)0SHI@Eabh9zRm!uB9RsmW9A4Z;2eABzjLU@_3Yb|{tzO}1YeB?~&EwGSvS z2b9-Gk@s+Bn7q;166{pOsgw*1jwq^ZTtTWtCL1hsmqk9p&jdx)T@RQl&dDjBieNJl zr|tj``9o2y>jP8GF7ag{X4W>)a%KhoKvyva1`M9A)97C%`B`O-U1bAu471WI(n_BRXdc33Qc~vQcM(m z%*7)yFC}Mk;$lTsaNBmW!75Q^;mHs)A-y`Vxw6QmkOqpmsncMpwYY?M85qRpg322J DDw4oP diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b415..ff23a68d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 4495da6b8704035486b377420d8b962d022bc1d3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 29 Jul 2025 12:09:45 +0900 Subject: [PATCH 111/178] more readme updates, installation instruction for all (#331) --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f9f4ca3f..940b478c 100644 --- a/README.md +++ b/README.md @@ -62,22 +62,65 @@ Required language/runtime versions: 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: +You will need to have: +- Swift (6.1.x+) +- Java (24+ for FFM, even though we support lower JDK targets) +- Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) + +### Preparing your environment + +Install **Swift**, the easiest way to do this is to use **Swiftly**: [swift.org/install/](https://www.swift.org/install/). +This should automatically install a recent Swift, but you can always make sure by running: + +```bash +swiftly install 6.1.2 --use +``` + +Install a recent enough Java distribution. We validate this project using Corretto so you can choose to use that as well, +however any recent enough Java distribution should work correctly. You can use sdkman to install Java: + +```bash +# Install sdkman from: https://sdkman.io +curl -s "https://get.sdkman.io" | bash +sdk install java 17.0.15-amzn +sdk install java 24.0.1-amzn + +sdk use java 24.0.1-amzn +``` + +The use of JDK 24 is required to build the project, even though the libraries being published may target lower Java versions. + +❗️ Please make sure to `export JAVA_HOME` such that swift-java can find the necessary java libraries! +When using sdkman the easiest way to export JAVA_HOME is to export the "current" used JDK's home, like this: + +```bash +export JAVA_HOME="$(sdk home java current) +``` + +### Testing your changes + +Many tests, including source generation tests, are written in Swift and you can execute them all by running the +swift package manager test command: ```bash > swift test ``` -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: +When adding tests in `Tests/...` targets, you can run these tests (or filter a specific test using `swift test --filter type-or-method-name`). + +Some tests are implemented in Java and therefore need to be executed using Gradle. +Please always use the gradle wrapper (`./gradlew`) to make sure to use the appropriate Gradle version ```bash > ./gradlew test ``` -Currently it is suggested to use Swift 6.0 and a Java 24+. +> Tip: A lot of the **runtime tests** for code relying on `jextract` are **located in sample apps**, +> so if you need to runtime test any code relying on source generation steps of jextract, consider adding the tests +> to an appropriate Sample. These tests are also executed in CI (which you can check in the `ci-validate.sh` script +> contained in every sample repository). -### Sample Apps +### Sample apps & tests Sample apps are located in the `Samples/` directory, and they showcase full "roundtrip" usage of the library and/or tools. @@ -101,12 +144,18 @@ To run a simple example app showcasing the jextract (Java calling Swift) approac This will also generate the necessary sources (by invoking jextract, extracting the `Sources/ExampleSwiftLibrary`) and generating Java sources in `src/generated/java`. +#### Other sample apps + +Please refer to the [Samples](Samples) directory for more sample apps which showcase the various usage modes of swift-java. + ## Benchmarks You can run Swift [ordo-one/package-benchmark](https://github.com/ordo-one/package-benchmark) and OpenJDK [JMH](https://github.com/openjdk/jmh) benchmarks in this project. Swift benchmarks are located under `Benchmarks/` and JMH benchmarks are currently part of the SwiftKit sample project: `Samples/SwiftKitSampleApp/src/jmh` because they depend on generated sources from the sample. +### Swift benchmarks + To run **Swift benchmarks** you can: ```bash @@ -114,6 +163,8 @@ cd Benchmarks swift package benchmark ``` +### Java benchmarks + In order to run JMH benchmarks you can: ```bash @@ -125,4 +176,20 @@ Please read documentation of both performance testing tools and understand that ## User Guide -More details about the project and how it can be used are available in [USER_GUIDE.md](USER_GUIDE.md) +More details about the project can be found in [docc](https://www.swift.org/documentation/docc/) documentation. + +To view the rendered docc documentation you can use the docc preview command: + +```bash +xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc + +# OR JavaKit to view JavaKit documentation: +# xcrun docc preview Sources/JavaKit/Documentation.docc + +# ======================================== +# Starting Local Preview Server +# Address: http://localhost:8080/documentation/documentation +# ======================================== +# Monitoring /Users/ktoso/code/swift-java/Sources/SwiftJavaDocumentation/Documentation.docc for changes... + +``` From 7f24d6276f59c34544a4c8f6771a8bc560aa5db9 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Wed, 30 Jul 2025 19:51:11 -0700 Subject: [PATCH 112/178] [JExtract] Translate generic parameters (#336) * Integrate `SwiftLexicalLookup` for unqualified type lookups * Introduce `SwiftTypeLookupContext` as a facade for `SwiftLexicalLookup` and a cached conversion from Syntax to `SwiftTypeDeclaration` instance. This wraps and replaces `SwiftSymbolTable`. * Introduce `SwiftGenericParameterDeclration` as a subclass of `SwiftTypeDeclaration` to represent generic parameters. * Store generic parameters and requirements in `SwiftFunctionSignature`. * `SwiftType.representativeConcreteTypeIn()` for getting `Data` from `T: DataProtocol` type. --- Package.swift | 1 + .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 75 ++++----- ...MSwift2JavaGenerator+JavaTranslation.swift | 81 ++++++---- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 4 +- ...wift2JavaGenerator+NativeTranslation.swift | 8 +- .../Swift2JavaTranslator.swift | 16 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 6 +- .../SwiftTypes/SwiftFunctionSignature.swift | 133 +++++++++++---- .../SwiftTypes/SwiftFunctionType.swift | 6 +- .../SwiftNominalTypeDeclaration.swift | 46 ++++-- .../SwiftTypes/SwiftParameter.swift | 4 +- .../SwiftParsedModuleSymbolTableBuilder.swift | 3 +- ...wiftType+RepresentativeConcreteeType.swift | 65 ++++++++ .../SwiftTypes/SwiftType.swift | 85 ++++++---- .../SwiftTypes/SwiftTypeLookupContext.swift | 151 ++++++++++++++++++ .../Asserts/TextAssertions.swift | 1 + .../JExtractSwiftTests/DataImportTests.swift | 29 ++-- .../FunctionLoweringTests.swift | 21 +++ 21 files changed, 567 insertions(+), 178 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift diff --git a/Package.swift b/Package.swift index 84b348c5..51608c31 100644 --- a/Package.swift +++ b/Package.swift @@ -422,6 +422,7 @@ let package = Package( name: "JExtractSwiftLib", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), + .product(name: "SwiftLexicalLookup", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index c2bb53ab..d2a71def 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .metatype, .optional, .tuple, .opaque, .existential: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -98,7 +98,7 @@ extension CFunction { enum CDeclToCLoweringError: Error { case invalidCDeclType(SwiftType) - case invalidNominalType(SwiftNominalTypeDeclaration) + case invalidNominalType(SwiftTypeDeclaration) case invalidFunctionConvention(SwiftFunctionType) } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index bb994164..085a6331 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -26,10 +26,10 @@ extension FFMSwift2JavaGenerator { ) throws -> LoweredFunctionSignature { let signature = try SwiftFunctionSignature( decl, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } /// Lower the given initializer to a C-compatible entrypoint, @@ -42,11 +42,11 @@ extension FFMSwift2JavaGenerator { ) throws -> LoweredFunctionSignature { let signature = try SwiftFunctionSignature( decl, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } /// Lower the given variable decl to a C-compatible entrypoint, @@ -66,10 +66,10 @@ extension FFMSwift2JavaGenerator { let signature = try SwiftFunctionSignature( decl, isSet: isSet, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } } @@ -98,7 +98,9 @@ struct CdeclLowering { try lowerParameter( selfParameter.type, convention: selfParameter.convention, - parameterName: selfParameter.parameterName ?? "self" + parameterName: selfParameter.parameterName ?? "self", + genericParameters: signature.genericParameters, + genericRequirements: signature.genericRequirements ) case nil, .initializer(_), .staticMethod(_): nil @@ -106,10 +108,12 @@ struct CdeclLowering { // Lower all of the parameters. let loweredParameters = try signature.parameters.enumerated().map { (index, param) in - try lowerParameter( + return try lowerParameter( param.type, convention: param.convention, - parameterName: param.parameterName ?? "_\(index)" + parameterName: param.parameterName ?? "_\(index)", + genericParameters: signature.genericParameters, + genericRequirements: signature.genericRequirements ) } @@ -142,7 +146,9 @@ struct CdeclLowering { func lowerParameter( _ type: SwiftType, convention: SwiftParameterConvention, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> LoweredParameter { // If there is a 1:1 mapping between this Swift type and a C type, we just // return it. @@ -257,7 +263,7 @@ struct CdeclLowering { guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else { throw LoweringError.unhandledType(type) } - return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) case .string: // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *') @@ -300,7 +306,7 @@ struct CdeclLowering { case .tuple(let tuple): if tuple.count == 1 { - return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName) + return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } if convention == .inout { throw LoweringError.inoutNotSupported(type) @@ -310,7 +316,7 @@ struct CdeclLowering { for (idx, element) in tuple.enumerated() { // FIXME: Use tuple element label. let cdeclName = "\(parameterName)_\(idx)" - let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName) + let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName, genericParameters: genericParameters, genericRequirements: genericRequirements) parameters.append(contentsOf: lowered.cdeclParameters) conversions.append(lowered.conversion) @@ -330,20 +336,14 @@ struct CdeclLowering { conversion: conversion ) - case .opaque(let proto), .existential(let proto): - // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` - // Translate it as the concrete type. - // NOTE: This is a temporary workaround until we add support for generics. - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { - return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName) + case .opaque, .existential, .genericParameter: + if let concreteTy = type.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(type) case .optional(let wrapped): - return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } } @@ -354,7 +354,9 @@ struct CdeclLowering { func lowerOptionalParameter( _ wrappedType: SwiftType, convention: SwiftParameterConvention, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> LoweredParameter { // If there is a 1:1 mapping between this Swift type and a C type, lower it to 'UnsafePointer?' if let _ = try? CType(cdeclType: wrappedType) { @@ -398,18 +400,15 @@ struct CdeclLowering { conversion: .pointee(.typedPointer(.optionalChain(.placeholder), swiftType: wrappedType)) ) - case .existential(let proto), .opaque(let proto): - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { - return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName) + case .existential, .opaque, .genericParameter: + if let concreteTy = wrappedType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(.optional(wrappedType)) case .tuple(let tuple): if tuple.count == 1 { - return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(.optional(wrappedType)) @@ -514,7 +513,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -668,7 +667,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .function, .optional, .existential, .opaque: + case .genericParameter, .function, .optional, .existential, .opaque: throw LoweringError.unhandledType(type) } } @@ -754,7 +753,9 @@ public struct LoweredFunctionSignature: Equatable { selfParameter: nil, parameters: allLoweredParameters, result: SwiftResult(convention: .direct, type: result.cdeclResultType), - effectSpecifiers: [] + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] ) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 9f658188..b29ca5d9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(symbolTable: self.symbolTable) + let translation = JavaTranslation(knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -115,8 +115,8 @@ extension FFMSwift2JavaGenerator { struct JavaTranslation { var knownTypes: SwiftKnownTypes - init(symbolTable: SwiftSymbolTable) { - self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) + init(knownTypes: SwiftKnownTypes) { + self.knownTypes = knownTypes } func translate( @@ -256,7 +256,9 @@ extension FFMSwift2JavaGenerator { convention: swiftSelf.convention, parameterName: swiftSelf.parameterName ?? "self", loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName + methodName: methodName, + genericParameters: swiftSignature.genericParameters, + genericRequirements: swiftSignature.genericRequirements ) } else { selfParameter = nil @@ -272,7 +274,9 @@ extension FFMSwift2JavaGenerator { convention: swiftParam.convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: swiftSignature.genericParameters, + genericRequirements: swiftSignature.genericRequirements ) } @@ -295,7 +299,9 @@ extension FFMSwift2JavaGenerator { convention: SwiftParameterConvention, parameterName: String, loweredParam: LoweredParameter, - methodName: String + methodName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // If there is a 1:1 mapping between this Swift type and a C type, that can @@ -352,7 +358,15 @@ extension FFMSwift2JavaGenerator { guard let genericArgs = swiftNominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unhandledType(swiftType) } - return try translateOptionalParameter(wrappedType: genericArgs[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: genericArgs[0], + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) case .string: return TranslatedParameter( @@ -399,20 +413,16 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) - case .existential(let proto), .opaque(let proto): - // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` - // Translate it as the concrete type. - // NOTE: This is a temporary workaround until we add support for generics. - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { + case .existential, .opaque, .genericParameter: + if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateParameter( type: concreteTy, convention: convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } @@ -420,7 +430,15 @@ extension FFMSwift2JavaGenerator { throw JavaTranslationError.unhandledType(swiftType) case .optional(let wrapped): - return try translateOptionalParameter(wrappedType: wrapped, convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: wrapped, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } } @@ -430,12 +448,14 @@ extension FFMSwift2JavaGenerator { convention: SwiftParameterConvention, parameterName: String, loweredParam: LoweredParameter, - methodName: String + methodName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // 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) { - var (translatedClass, lowerFunc) = switch cType.javaType { + let (translatedClass, lowerFunc) = switch cType.javaType { case .int: ("OptionalInt", "toOptionalSegmentInt") case .long: ("OptionalLong", "toOptionalSegmentLong") case .double: ("OptionalDouble", "toOptionalSegmentDouble") @@ -473,23 +493,30 @@ extension FFMSwift2JavaGenerator { ], conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false) ) - case .existential(let proto), .opaque(let proto): - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { + case .existential, .opaque, .genericParameter: + if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateOptionalParameter( wrappedType: concreteTy, convention: convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } throw JavaTranslationError.unhandledType(.optional(swiftType)) case .tuple(let tuple): if tuple.count == 1 { - return try translateOptionalParameter(wrappedType: tuple[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: tuple[0], + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } throw JavaTranslationError.unhandledType(.optional(swiftType)) default: @@ -580,7 +607,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .optional, .function, .existential, .opaque: + case .genericParameter, .optional, .function, .existential, .opaque: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 10dab4b9..1e32d1e5 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,7 +125,7 @@ extension FFMSwift2JavaGenerator { } func printSwiftThunkImports(_ printer: inout CodePrinter) { - for module in self.symbolTable.importedModules.keys.sorted() { + for module in self.lookupContext.symbolTable.importedModules.keys.sorted() { guard module != "Swift" else { continue } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index a24dabb1..d30d3e74 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -24,7 +24,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String - let symbolTable: SwiftSymbolTable + let lookupContext: SwiftTypeLookupContext var javaPackagePath: String { javaPackage.replacingOccurrences(of: ".", with: "/") @@ -51,7 +51,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.javaPackage = javaPackage self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory - self.symbolTable = translator.symbolTable + self.lookupContext = translator.lookupContext // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 57ea15b2..8d18e6ae 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -216,7 +216,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .existential, .opaque: + case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -251,7 +251,7 @@ extension JNISwift2JavaGenerator { case .tuple([]): return TranslatedResult(javaType: .void, conversion: .placeholder) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 5ccefadb..48a6e8d4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -129,7 +129,7 @@ extension JNISwift2JavaGenerator { ) ) - case .metatype, .optional, .tuple, .existential, .opaque: + case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } } @@ -160,7 +160,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -187,7 +187,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -223,7 +223,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index d1141e5f..3e2359f2 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -23,7 +23,7 @@ import SwiftSyntax public final class Swift2JavaTranslator { static let SWIFT_INTERFACE_SUFFIX = ".swiftinterface" - package var log = Logger(label: "translator", logLevel: .info) + package var log: Logger let config: Configuration @@ -52,7 +52,11 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - package var symbolTable: SwiftSymbolTable! = nil + var lookupContext: SwiftTypeLookupContext! = nil + + var symbolTable: SwiftSymbolTable! { + return lookupContext?.symbolTable + } public init( config: Configuration @@ -60,6 +64,7 @@ public final class Swift2JavaTranslator { 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.log = Logger(label: "translator", logLevel: config.logLevel ?? .info) self.config = config self.swiftModuleName = swiftModule } @@ -116,11 +121,12 @@ extension Swift2JavaTranslator { package func prepareForTranslation() { let dependenciesSource = self.buildDependencyClassesSourceFile() - self.symbolTable = SwiftSymbolTable.setup( + let symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, inputs.map({ $0.syntax }) + [dependenciesSource], log: self.log ) + self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) } /// Check if any of the imported decls uses a nominal declaration that satisfies @@ -140,6 +146,8 @@ extension Swift2JavaTranslator { return check(ty) case .existential(let ty), .opaque(let ty): return check(ty) + case .genericParameter: + return false } } @@ -206,7 +214,7 @@ extension Swift2JavaTranslator { func importedNominalType( _ typeNode: TypeSyntax ) -> ImportedNominalType? { - guard let swiftType = try? SwiftType(typeNode, symbolTable: self.symbolTable) else { + guard let swiftType = try? SwiftType(typeNode, lookupContext: lookupContext) else { return nil } guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else { diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 7ce982a2..90181757 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -104,7 +104,7 @@ final class Swift2JavaVisitor { signature = try SwiftFunctionSignature( node, enclosingType: typeContext?.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) } catch { self.log.debug("Failed to import: '\(node.qualifiedNameForDebug)'; \(error)") @@ -145,7 +145,7 @@ final class Swift2JavaVisitor { node, isSet: kind == .setter, enclosingType: typeContext?.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) let imported = ImportedFunc( @@ -193,7 +193,7 @@ final class Swift2JavaVisitor { signature = try SwiftFunctionSignature( node, enclosingType: typeContext.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 5cca79bf..85b96110 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -15,6 +15,11 @@ import SwiftSyntax import SwiftSyntaxBuilder +enum SwiftGenericRequirement: Equatable { + case inherits(SwiftType, SwiftType) + case equals(SwiftType, SwiftType) +} + /// Provides a complete signature for a Swift function, which includes its /// parameters and return type. public struct SwiftFunctionSignature: Equatable { @@ -22,17 +27,23 @@ public struct SwiftFunctionSignature: Equatable { var parameters: [SwiftParameter] var result: SwiftResult var effectSpecifiers: [SwiftEffectSpecifier] + var genericParameters: [SwiftGenericParameterDeclaration] + var genericRequirements: [SwiftGenericRequirement] init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult, - effectSpecifiers: [SwiftEffectSpecifier] + effectSpecifiers: [SwiftEffectSpecifier], + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) { self.selfParameter = selfParameter self.parameters = parameters self.result = result self.effectSpecifiers = effectSpecifiers + self.genericParameters = genericParameters + self.genericRequirements = genericRequirements } } @@ -54,13 +65,8 @@ extension SwiftFunctionSignature { init( _ node: InitializerDeclSyntax, enclosingType: SwiftType?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } - guard let enclosingType else { throw SwiftFunctionTranslationError.missingEnclosingType(node) } @@ -70,33 +76,36 @@ extension SwiftFunctionSignature { throw SwiftFunctionTranslationError.failableInitializer(node) } - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } - + let (genericParams, genericRequirements) = try Self.translateGenericParameters( + parameterClause: node.genericParameterClause, + whereClause: node.genericWhereClause, + lookupContext: lookupContext + ) let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( node.signature, - symbolTable: symbolTable + lookupContext: lookupContext ) self.init( selfParameter: .initializer(enclosingType), parameters: parameters, result: SwiftResult(convention: .direct, type: enclosingType), - effectSpecifiers: effectSpecifiers + effectSpecifiers: effectSpecifiers, + genericParameters: genericParams, + genericRequirements: genericRequirements ) } init( _ node: FunctionDeclSyntax, enclosingType: SwiftType?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } + let (genericParams, genericRequirements) = try Self.translateGenericParameters( + parameterClause: node.genericParameterClause, + whereClause: node.genericWhereClause, + lookupContext: lookupContext + ) // 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. @@ -132,7 +141,7 @@ extension SwiftFunctionSignature { // Translate the parameters. let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( node.signature, - symbolTable: symbolTable + lookupContext: lookupContext ) // Translate the result type. @@ -140,20 +149,81 @@ extension SwiftFunctionSignature { if let resultType = node.signature.returnClause?.type { result = try SwiftResult( convention: .direct, - type: SwiftType(resultType, symbolTable: symbolTable) + type: SwiftType(resultType, lookupContext: lookupContext) ) } else { result = .void } - self.init(selfParameter: selfParameter, parameters: parameters, result: result, effectSpecifiers: effectSpecifiers) + self.init( + selfParameter: selfParameter, + parameters: parameters, + result: result, + effectSpecifiers: effectSpecifiers, + genericParameters: genericParams, + genericRequirements: genericRequirements + ) + } + + static func translateGenericParameters( + parameterClause: GenericParameterClauseSyntax?, + whereClause: GenericWhereClauseSyntax?, + lookupContext: SwiftTypeLookupContext + ) throws -> (parameters: [SwiftGenericParameterDeclaration], requirements: [SwiftGenericRequirement]) { + var params: [SwiftGenericParameterDeclaration] = [] + var requirements: [SwiftGenericRequirement] = [] + + // Parameter clause + if let parameterClause { + for parameterNode in parameterClause.parameters { + guard parameterNode.specifier == nil else { + throw SwiftFunctionTranslationError.genericParameterSpecifier(parameterNode) + } + let param = try lookupContext.typeDeclaration(for: parameterNode) as! SwiftGenericParameterDeclaration + params.append(param) + if let inheritedNode = parameterNode.inheritedType { + let inherited = try SwiftType(inheritedNode, lookupContext: lookupContext) + requirements.append(.inherits(.genericParameter(param), inherited)) + } + } + } + + // Where clause + if let whereClause { + for requirementNode in whereClause.requirements { + let requirement: SwiftGenericRequirement + switch requirementNode.requirement { + case .conformanceRequirement(let conformance): + requirement = .inherits( + try SwiftType(conformance.leftType, lookupContext: lookupContext), + try SwiftType(conformance.rightType, lookupContext: lookupContext) + ) + case .sameTypeRequirement(let sameType): + guard let leftType = sameType.leftType.as(TypeSyntax.self) else { + throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) + } + guard let rightType = sameType.rightType.as(TypeSyntax.self) else { + throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) + } + requirement = .equals( + try SwiftType(leftType, lookupContext: lookupContext), + try SwiftType(rightType, lookupContext: lookupContext) + ) + case .layoutRequirement: + throw SwiftFunctionTranslationError.layoutRequirement(requirementNode) + } + requirements.append(requirement) + } + } + + return (params, requirements) } /// Translate the function signature, returning the list of translated /// parameters and effect specifiers. static func translateFunctionSignature( _ signature: FunctionSignatureSyntax, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) { var effectSpecifiers = [SwiftEffectSpecifier]() if signature.effectSpecifiers?.throwsClause != nil { @@ -164,13 +234,18 @@ extension SwiftFunctionSignature { } let parameters = try signature.parameterClause.parameters.map { param in - try SwiftParameter(param, symbolTable: symbolTable) + try SwiftParameter(param, lookupContext: lookupContext) } return (parameters, effectSpecifiers) } - init(_ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable) throws { + init( + _ varNode: VariableDeclSyntax, + isSet: Bool, + enclosingType: SwiftType?, + lookupContext: SwiftTypeLookupContext + ) 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. @@ -205,7 +280,7 @@ extension SwiftFunctionSignature { guard let varTypeNode = binding.typeAnnotation?.type else { throw SwiftFunctionTranslationError.missingTypeAnnotation(varNode) } - let valueType = try SwiftType(varTypeNode, symbolTable: symbolTable) + let valueType = try SwiftType(varTypeNode, lookupContext: lookupContext) var effectSpecifiers: [SwiftEffectSpecifier]? = nil switch binding.accessorBlock?.accessors { @@ -230,6 +305,8 @@ extension SwiftFunctionSignature { self.parameters = [] self.result = .init(convention: .direct, type: valueType) } + self.genericParameters = [] + self.genericRequirements = [] } private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] { @@ -287,11 +364,13 @@ extension VariableDeclSyntax { enum SwiftFunctionTranslationError: Error { case `throws`(ThrowsClauseSyntax) case async(TokenSyntax) - case generic(GenericParameterClauseSyntax) case classMethod(TokenSyntax) case missingEnclosingType(InitializerDeclSyntax) case failableInitializer(InitializerDeclSyntax) case multipleBindings(VariableDeclSyntax) case missingTypeAnnotation(VariableDeclSyntax) case unsupportedAccessor(AccessorDeclSyntax) + case genericParameterSpecifier(GenericParameterSyntax) + case expressionInGenericRequirement(GenericRequirementSyntax) + case layoutRequirement(GenericRequirementSyntax) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift index 565a24c3..da6fd2a2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift @@ -40,18 +40,18 @@ extension SwiftFunctionType { init( _ node: FunctionTypeSyntax, convention: Convention, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) 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) + type: try SwiftType(param.type, lookupContext: lookupContext) ) } - self.resultType = try SwiftType(node.returnClause.type, symbolTable: symbolTable) + self.resultType = try SwiftType(node.returnClause.type, lookupContext: lookupContext) // check for effect specifiers if let throwsClause = node.effectSpecifiers?.throwsClause { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 8be645c8..b377fd85 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -18,9 +18,24 @@ import SwiftSyntax @_spi(Testing) public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax +package class SwiftTypeDeclaration { + /// 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. + let moduleName: String + + /// The name of this nominal type, e.g., 'MyCollection'. + let name: String + + init(moduleName: String, name: String) { + self.moduleName = moduleName + self.name = name + } +} + /// 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. -package class SwiftNominalTypeDeclaration { +package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { enum Kind { case actor case `class` @@ -40,14 +55,6 @@ package class SwiftNominalTypeDeclaration { /// MyCollection.Iterator. let 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. - let moduleName: String - - /// The name of this nominal type, e.g., 'MyCollection'. - let name: String - // TODO: Generic parameters. /// Identify this nominal declaration as one of the known standard library @@ -63,9 +70,7 @@ package class SwiftNominalTypeDeclaration { parent: SwiftNominalTypeDeclaration?, node: NominalTypeDeclSyntaxNode ) { - self.moduleName = moduleName self.parent = parent - self.name = node.name.text self.syntax = node // Determine the kind from the syntax node. @@ -77,6 +82,7 @@ package class SwiftNominalTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } + super.init(moduleName: moduleName, name: node.name.text) } /// Determine the known standard library type for this nominal type @@ -107,13 +113,25 @@ package class SwiftNominalTypeDeclaration { } } -extension SwiftNominalTypeDeclaration: Equatable { - package static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool { +package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { + let syntax: GenericParameterSyntax + + init( + moduleName: String, + node: GenericParameterSyntax + ) { + self.syntax = node + super.init(moduleName: moduleName, name: node.name.text) + } +} + +extension SwiftTypeDeclaration: Equatable { + package static func ==(lhs: SwiftTypeDeclaration, rhs: SwiftTypeDeclaration) -> Bool { lhs === rhs } } -extension SwiftNominalTypeDeclaration: Hashable { +extension SwiftTypeDeclaration: Hashable { package func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift index 24b3d8b4..75d165e9 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift @@ -58,7 +58,7 @@ enum SwiftParameterConvention: Equatable { } extension SwiftParameter { - init(_ node: FunctionParameterSyntax, symbolTable: SwiftSymbolTable) throws { + init(_ node: FunctionParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { // Determine the convention. The default is by-value, but there are // specifiers on the type for other conventions (like `inout`). var type = node.type @@ -90,7 +90,7 @@ extension SwiftParameter { self.convention = convention // Determine the type. - self.type = try SwiftType(type, symbolTable: symbolTable) + self.type = try SwiftType(type, lookupContext: lookupContext) // FIXME: swift-syntax itself should have these utilities based on identifiers. if let secondName = node.secondName { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 4026e624..9b8ef236 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -122,7 +122,8 @@ extension SwiftParsedModuleSymbolTableBuilder { parsedModule: symbolTable, importedModules: importedModules ) - guard let extendedType = try? SwiftType(node.extendedType, symbolTable: table) else { + let lookupContext = SwiftTypeLookupContext(symbolTable: table) + guard let extendedType = try? SwiftType(node.extendedType, lookupContext: lookupContext) else { return false } guard let extendedNominal = extendedType.asNominalTypeDeclaration else { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift new file mode 100644 index 00000000..ce668485 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftType { + /// Returns a concrete type if this is a generic parameter in the list and it + /// conforms to a protocol with representative concrete type. + func representativeConcreteTypeIn( + knownTypes: SwiftKnownTypes, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) -> SwiftType? { + return representativeConcreteType( + self, + knownTypes: knownTypes, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } +} + +private func representativeConcreteType( + _ type: SwiftType, + knownTypes: SwiftKnownTypes, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] +) -> SwiftType? { + var maybeProto: SwiftType? = nil + switch type { + case .existential(let proto), .opaque(let proto): + maybeProto = proto + case .genericParameter(let genericParam): + // If the type is a generic parameter declared in this function and + // conforms to a protocol with representative concrete type, use it. + if genericParameters.contains(genericParam) { + for requirement in genericRequirements { + if case .inherits(let left, let right) = requirement, left == type { + guard maybeProto == nil else { + // multiple requirements on the generic parameter. + return nil + } + maybeProto = right + break + } + } + } + default: + return nil + } + + if let knownProtocol = maybeProto?.asNominalTypeDeclaration?.knownTypeKind { + return knownTypes.representativeType(of: knownProtocol) + } + return nil +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index d0f1f98d..da738d39 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -18,6 +18,8 @@ import SwiftSyntax enum SwiftType: Equatable { case nominal(SwiftNominalType) + case genericParameter(SwiftGenericParameterDeclaration) + indirect case function(SwiftFunctionType) /// `.Type` @@ -43,7 +45,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .function, .metatype, .optional, .existential, .opaque: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque: nil } } @@ -86,7 +88,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .optional, .tuple, .existential, .opaque: + case .genericParameter, .optional, .tuple, .existential, .opaque: return false } } @@ -98,13 +100,14 @@ extension SwiftType: CustomStringConvertible { private var postfixRequiresParentheses: Bool { switch self { case .function, .existential, .opaque: true - case .metatype, .nominal, .optional, .tuple: false + case .genericParameter, .metatype, .nominal, .optional, .tuple: false } } var description: String { switch self { case .nominal(let nominal): return nominal.description + case .genericParameter(let genericParam): return genericParam.name case .function(let functionType): return functionType.description case .metatype(let instanceType): var instanceTypeStr = instanceType.description @@ -179,7 +182,7 @@ extension SwiftNominalType { } extension SwiftType { - init(_ type: TypeSyntax, symbolTable: SwiftSymbolTable) throws { + init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .compositionType, .dictionaryType, .missingType, .namedOpaqueReturnType, @@ -192,7 +195,7 @@ extension SwiftType { // FIXME: This string matching is a horrible hack. switch attributedType.attributes.trimmedDescription { case "@convention(c)", "@convention(swift)": - let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable) + let innerType = try SwiftType(attributedType.baseType, lookupContext: lookupContext) switch innerType { case .function(var functionType): let isConventionC = attributedType.attributes.trimmedDescription == "@convention(c)" @@ -208,7 +211,7 @@ extension SwiftType { case .functionType(let functionType): self = .function( - try SwiftFunctionType(functionType, convention: .swift, symbolTable: symbolTable) + try SwiftFunctionType(functionType, convention: .swift, lookupContext: lookupContext) ) case .identifierType(let identifierType): @@ -217,7 +220,7 @@ extension SwiftType { try genericArgumentClause.arguments.map { argument in switch argument.argument { case .type(let argumentTy): - try SwiftType(argumentTy, symbolTable: symbolTable) + try SwiftType(argumentTy, lookupContext: lookupContext) default: throw TypeTranslationError.unimplementedType(type) } @@ -228,13 +231,13 @@ extension SwiftType { self = try SwiftType( originalType: type, parent: nil, - name: identifierType.name.text, + name: identifierType.name, genericArguments: genericArgs, - symbolTable: symbolTable + lookupContext: lookupContext ) case .implicitlyUnwrappedOptionalType(let optionalType): - self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + self = .optional(try SwiftType(optionalType.wrappedType, lookupContext: lookupContext)) case .memberType(let memberType): // If the parent type isn't a known module, translate it. @@ -244,7 +247,7 @@ extension SwiftType { if memberType.baseType.trimmedDescription == "Swift" { parentType = nil } else { - parentType = try SwiftType(memberType.baseType, symbolTable: symbolTable) + parentType = try SwiftType(memberType.baseType, lookupContext: lookupContext) } // Translate the generic arguments. @@ -252,7 +255,7 @@ extension SwiftType { try genericArgumentClause.arguments.map { argument in switch argument.argument { case .type(let argumentTy): - try SwiftType(argumentTy, symbolTable: symbolTable) + try SwiftType(argumentTy, lookupContext: lookupContext) default: throw TypeTranslationError.unimplementedType(type) } @@ -262,27 +265,27 @@ extension SwiftType { self = try SwiftType( originalType: type, parent: parentType, - name: memberType.name.text, + name: memberType.name, genericArguments: genericArgs, - symbolTable: symbolTable + lookupContext: lookupContext ) case .metatypeType(let metatypeType): - self = .metatype(try SwiftType(metatypeType.baseType, symbolTable: symbolTable)) + self = .metatype(try SwiftType(metatypeType.baseType, lookupContext: lookupContext)) case .optionalType(let optionalType): - self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + self = .optional(try SwiftType(optionalType.wrappedType, lookupContext: lookupContext)) case .tupleType(let tupleType): self = try .tuple(tupleType.elements.map { element in - try SwiftType(element.type, symbolTable: symbolTable) + try SwiftType(element.type, lookupContext: lookupContext) }) case .someOrAnyType(let someOrAntType): if someOrAntType.someOrAnySpecifier.tokenKind == .keyword(.some) { - self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } else { - self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } } } @@ -290,25 +293,37 @@ extension SwiftType { init( originalType: TypeSyntax, parent: SwiftType?, - name: String, + name: TokenSyntax, genericArguments: [SwiftType]?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) 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) + let typeDecl: SwiftTypeDeclaration? + if let parent { + guard let parentDecl = parent.asNominalTypeDeclaration else { + throw TypeTranslationError.unknown(originalType) + } + typeDecl = lookupContext.symbolTable.lookupNestedType(name.text, parent: parentDecl) + } else { + typeDecl = try lookupContext.unqualifiedLookup(name: Identifier(name)!, from: name) + } + guard let typeDecl else { + throw TypeTranslationError.unknown(originalType) } - self = .nominal( - SwiftNominalType( - parent: parent?.asNominalType, - nominalTypeDecl: nominalTypeDecl, - genericArguments: genericArguments + if let nominalDecl = typeDecl as? SwiftNominalTypeDeclaration { + self = .nominal( + SwiftNominalType( + parent: parent?.asNominalType, + nominalTypeDecl: nominalDecl, + genericArguments: genericArguments + ) ) - ) + } else if let genericParamDecl = typeDecl as? SwiftGenericParameterDeclaration { + self = .genericParameter(genericParamDecl) + } else { + fatalError("unknown SwiftTypeDeclaration: \(type(of: typeDecl))") + } } init?( @@ -345,11 +360,11 @@ extension SwiftType { enum TypeTranslationError: Error { /// We haven't yet implemented support for this type. - case unimplementedType(TypeSyntax) + case unimplementedType(TypeSyntax, file: StaticString = #file, line: Int = #line) /// Missing generic arguments. - case missingGenericArguments(TypeSyntax) + case missingGenericArguments(TypeSyntax, file: StaticString = #file, line: Int = #line) /// Unknown nominal type. - case unknown(TypeSyntax) + case unknown(TypeSyntax, file: StaticString = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift new file mode 100644 index 00000000..9ede2b1b --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -0,0 +1,151 @@ +//===----------------------------------------------------------------------===// +// +// 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 +@_spi(Experimental) import SwiftLexicalLookup + +/// Unqualified type lookup manager. +/// All unqualified lookup should be done via this instance. This caches the +/// association of `Syntax.ID` to `SwiftTypeDeclaration`, and guarantees that +/// there's only one `SwiftTypeDeclaration` per declaration `Syntax`. +class SwiftTypeLookupContext { + var symbolTable: SwiftSymbolTable + + private var typeDecls: [Syntax.ID: SwiftTypeDeclaration] = [:] + + init(symbolTable: SwiftSymbolTable) { + self.symbolTable = symbolTable + } + + /// Perform unqualified type lookup. + /// + /// - Parameters: + /// - name: name to lookup + /// - node: `Syntax` node the lookup happened + func unqualifiedLookup(name: Identifier, from node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + + for result in node.lookup(name) { + switch result { + case .fromScope(_, let names): + if !names.isEmpty { + return typeDeclaration(for: names) + } + + case .fromFileScope(_, let names): + if !names.isEmpty { + return typeDeclaration(for: names) + } + + case .lookInMembers(let scopeNode): + if let nominalDecl = try typeDeclaration(for: scopeNode) { + if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { + return found + } + } + + case .lookInGenericParametersOfExtendedType(let extensionNode): + // TODO: Implement + _ = extensionNode + break + + case .mightIntroduceDollarIdentifiers: + // Dollar identifier can't be a type, ignore. + break + } + } + + // Fallback to global symbol table lookup. + return symbolTable.lookupTopLevelNominalType(name.name) + } + + /// Find the first type declaration in the `LookupName` results. + private func typeDeclaration(for names: [LookupName]) -> SwiftTypeDeclaration? { + for name in names { + switch name { + case .identifier(let identifiableSyntax, _): + return try? typeDeclaration(for: identifiableSyntax) + case .declaration(let namedDeclSyntax): + return try? typeDeclaration(for: namedDeclSyntax) + case .implicit(let implicitDecl): + // TODO: Implement + _ = implicitDecl + break + case .dollarIdentifier: + break + } + } + return nil + } + + /// Returns the type declaration object associated with the `Syntax` node. + /// If there's no declaration created, create an instance on demand, and cache it. + func typeDeclaration(for node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + if let found = typeDecls[node.id] { + return found + } + + let typeDecl: SwiftTypeDeclaration + switch Syntax(node).as(SyntaxEnum.self) { + case .genericParameter(let node): + typeDecl = SwiftGenericParameterDeclaration(moduleName: symbolTable.moduleName, node: node) + case .classDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .actorDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .structDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .enumDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .protocolDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .typeAliasDecl: + fatalError("typealias not implemented") + case .associatedTypeDecl: + fatalError("associatedtype not implemented") + default: + throw TypeLookupError.notType(Syntax(node)) + } + + typeDecls[node.id] = typeDecl + return typeDecl + } + + /// Create a nominal type declaration instance for the specified syntax node. + private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode) throws -> SwiftNominalTypeDeclaration { + SwiftNominalTypeDeclaration( + moduleName: self.symbolTable.moduleName, + parent: try parentTypeDecl(for: node), + node: node + ) + } + + /// Find a parent nominal type declaration of the specified syntax node. + private func parentTypeDecl(for node: some DeclSyntaxProtocol) throws -> SwiftNominalTypeDeclaration? { + var node: DeclSyntax = DeclSyntax(node) + while let parentDecl = node.ancestorDecl { + switch parentDecl.as(DeclSyntaxEnum.self) { + case .structDecl, .classDecl, .actorDecl, .enumDecl, .protocolDecl: + return (try typeDeclaration(for: parentDecl) as! SwiftNominalTypeDeclaration) + default: + node = parentDecl + continue + } + } + return nil + } +} + +enum TypeLookupError: Error { + case notType(Syntax) +} diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 4b0162f3..ffefe907 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -37,6 +37,7 @@ func assertOutput( column: Int = #column ) throws { var config = Configuration() + config.logLevel = .trace config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index af71c157..7416779d 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -28,7 +28,7 @@ final class DataImportTests { """ import Foundation - public func receiveDataProtocol(dat: some DataProtocol) + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ @@ -342,9 +342,9 @@ final class DataImportTests { import Foundation """, """ - @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat") - public func swiftjava_SwiftModule_receiveDataProtocol_dat(_ dat: UnsafeRawPointer) { - receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee) + @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2") + public func swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(_ dat: UnsafeRawPointer, _ dat2: UnsafeRawPointer?) { + receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee, dat2: dat2?.assumingMemoryBound(to: Data.self).pointee) } """, @@ -365,22 +365,23 @@ final class DataImportTests { """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_receiveDataProtocol_dat(const void *dat) + * void swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(const void *dat, const void *dat2) * } */ - private static class swiftjava_SwiftModule_receiveDataProtocol_dat { + private static class swiftjava_SwiftModule_receiveDataProtocol_dat_dat2 { private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* dat: */SwiftValueLayout.SWIFT_POINTER + /* dat: */SwiftValueLayout.SWIFT_POINTER, + /* dat2: */SwiftValueLayout.SWIFT_POINTER ); private static final MemorySegment ADDR = - SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat"); + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2"); private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); - public static void call(java.lang.foreign.MemorySegment dat) { + public static void call(java.lang.foreign.MemorySegment dat, java.lang.foreign.MemorySegment dat2) { try { if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall(dat); + CallTraces.traceDowncall(dat, dat2); } - HANDLE.invokeExact(dat); + HANDLE.invokeExact(dat, dat2); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -392,11 +393,11 @@ final class DataImportTests { /** * Downcall to Swift: * {@snippet lang=swift : - * public func receiveDataProtocol(dat: some DataProtocol) + * public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) * } */ - public static void receiveDataProtocol(Data dat) { - swiftjava_SwiftModule_receiveDataProtocol_dat.call(dat.$memorySegment()); + public static void receiveDataProtocol(Data dat, Optional dat2) { + swiftjava_SwiftModule_receiveDataProtocol_dat_dat2.call(dat.$memorySegment(), SwiftRuntime.toOptionalSegmentInstance(dat2)); } """, diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 8419ec61..ba1aad06 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -404,6 +404,27 @@ final class FunctionLoweringTests { """) } + @Test("Lowering generic parameters") + func genericParam() throws { + try assertLoweredFunction( + """ + func fn(x: T, y: U?) where T: DataProtocol + """, + sourceFile: """ + import Foundation + """, + expectedCDecl: """ + @_cdecl("c_fn") + public func c_fn(_ x: UnsafeRawPointer, _ y: UnsafeRawPointer?) { + fn(x: x.assumingMemoryBound(to: Data.self).pointee, y: y?.assumingMemoryBound(to: Data.self).pointee) + } + """, + expectedCFunction: """ + void c_fn(const void *x, const void *y) + """ + ) + } + @Test("Lowering read accessor") func lowerGlobalReadAccessor() throws { try assertLoweredVariableAccessor( From ac30ee40131c3062ff1131d74bcc9a1d493bcf8c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 31 Jul 2025 17:25:52 +0900 Subject: [PATCH 113/178] Initial support for unsigned numbers in jextract (#333) --- .unacceptablelanguageignore | 3 +- .../MySwiftLibrary/MySwiftLibrary.swift | 5 + .../com/example/swift/MySwiftLibraryTest.java | 7 + .../Sources/MySwiftLibrary/MySwiftClass.swift | 12 + .../example/swift/UnsignedNumbersTest.java | 36 +++ .../Common/TypeAnnotations.swift | 26 ++ .../Configuration+Extensions.swift | 25 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 40 ++- ...MSwift2JavaGenerator+JavaTranslation.swift | 112 +++++++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../FFM/FFMSwift2JavaGenerator.swift | 14 +- .../FFM/ForeignValueLayouts.swift | 8 + Sources/JExtractSwiftLib/ImportedDecls.swift | 9 + .../JNI/JNIJavaTypeTranslator.swift | 57 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 136 ++++++--- ...wift2JavaGenerator+NativeTranslation.swift | 16 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JNI/JNISwift2JavaGenerator.swift | 7 +- Sources/JExtractSwiftLib/JNI/JNIType.swift | 2 +- Sources/JExtractSwiftLib/JavaParameter.swift | 17 +- .../JavaType+JDK.swift} | 0 .../JavaTypes/JavaType+SwiftKit.swift | 88 ++++++ Sources/JExtractSwiftLib/Swift2Java.swift | 2 + .../SwiftTypes/SwiftType.swift | 11 + .../BridgedValues/JavaValue+Integers.swift | 178 ++++++++++++ .../Configuration.swift | 5 + .../GenerationMode.swift | 44 +++ Sources/JavaTypes/JavaAnnotation.swift | 47 +++ Sources/JavaTypes/JavaType+JavaSource.swift | 15 +- Sources/JavaTypes/JavaType+SwiftNames.swift | 44 +-- Sources/JavaTypes/JavaType.swift | 34 ++- .../Documentation.docc/SupportedFeatures.md | 70 ++++- .../SwiftJavaLib/JavaClassTranslator.swift | 6 +- .../Commands/JExtractCommand.swift | 32 +++ .../Commands/ResolveCommand.swift | 2 +- Sources/_Subprocess/Configuration.swift | 2 +- SwiftKitCore/build.gradle | 16 +- .../swiftkit/core/NotImplementedError.java | 24 ++ .../swift/swiftkit/core/Preconditions.java | 153 ++++++++++ .../swiftkit/core/annotations/NonNull.java | 28 ++ .../swiftkit/core/annotations/Nullable.java | 28 ++ .../swiftkit/core/annotations/Unsigned.java | 42 +++ .../swift/swiftkit/ffm/SwiftValueLayout.java | 10 +- .../Asserts/LoweringAssertions.swift | 2 + .../Asserts/TextAssertions.swift | 6 +- .../FuncCallbackImportTests.swift | 4 +- .../FunctionDescriptorImportTests.swift | 2 + .../JNI/JNIClassTests.swift | 172 +++++------ .../JNI/JNIModuleTests.swift | 5 + .../JNI/JNIStructTests.swift | 128 +++++---- .../JNI/JNIUnsignedNumberTests.swift | 154 ++++++++++ .../JNI/JNIVariablesTests.swift | 1 + .../MethodImportTests.swift | 9 + .../UnsignedNumberTests.swift | 268 ++++++++++++++++++ 55 files changed, 1922 insertions(+), 268 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java create mode 100644 Sources/JExtractSwiftLib/Common/TypeAnnotations.swift create mode 100644 Sources/JExtractSwiftLib/Configuration+Extensions.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift rename Sources/JExtractSwiftLib/{JavaConstants/JavaTypes.swift => JavaTypes/JavaType+JDK.swift} (100%) create mode 100644 Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift create mode 100644 Sources/JavaTypes/JavaAnnotation.swift create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift create mode 100644 Tests/JExtractSwiftTests/UnsignedNumberTests.swift diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore index 92a1d726..c3f97d3d 100644 --- a/.unacceptablelanguageignore +++ b/.unacceptablelanguageignore @@ -3,4 +3,5 @@ Sources/_Subprocess/Platforms/Subprocess+Darwin.swift Sources/_Subprocess/Platforms/Subprocess+Linux.swift Sources/_Subprocess/Platforms/Subprocess+Unix.swift Sources/_Subprocess/Teardown.swift -Sources/_Subprocess/Subprocess.swift \ No newline at end of file +Sources/_Subprocess/Subprocess.swift +NOTICE.txt \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 1dd84547..09903638 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -45,6 +45,11 @@ public func globalTakeIntInt(i: Int64, j: Int64) { p("i:\(i), j:\(j)") } +public func echoUnsignedInt(i: UInt32, j: UInt64) -> UInt64 { + p("i:\(i), j:\(j)") + return UInt64(i) + j +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index 5c9c2358..6da2fd4b 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -61,4 +61,11 @@ void globalVariable() { MySwiftLibrary.setGlobalVariable(100); assertEquals(100, MySwiftLibrary.getGlobalVariable()); } + + @Test + void globalUnsignedIntEcho() { + int i = 12; + long l = 1200; + assertEquals(1212, MySwiftLibrary.echoUnsignedInt(12, 1200)); + } } \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 30661045..e1139c2b 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -60,4 +60,16 @@ public class MySwiftClass { public func makeRandomIntMethod() -> Int { return Int.random(in: 1..<256) } + + public func takeUnsignedChar(arg: UInt16) { + p("\(UInt32.self) = \(arg)") + } + + public func takeUnsignedInt(arg: UInt32) { + p("\(UInt32.self) = \(arg)") + } + + public func takeUnsignedLong(arg: UInt64) { + p("\(UInt64.self) = \(arg)") + } } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java new file mode 100644 index 00000000..beb0f817 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +public class UnsignedNumbersTest { + @Test + void take_uint32() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var c = MySwiftClass.init(1, 2, arena); + c.takeUnsignedInt(128); + } + } + + @Test + void take_uint64() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var c = MySwiftClass.init(1, 2, arena); + c.takeUnsignedLong(Long.MAX_VALUE); + } + } +} diff --git a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift new file mode 100644 index 00000000..70a86e82 --- /dev/null +++ b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaKitConfigurationShared + +/// Determine if the given type needs any extra annotations that should be included +/// in Java sources when the corresponding Java type is rendered. +func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] { + if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate { + return [JavaAnnotation.unsigned] + } + + return [] +} diff --git a/Sources/JExtractSwiftLib/Configuration+Extensions.swift b/Sources/JExtractSwiftLib/Configuration+Extensions.swift new file mode 100644 index 00000000..d85cf447 --- /dev/null +++ b/Sources/JExtractSwiftLib/Configuration+Extensions.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared +import JavaTypes // TODO: this should become SwiftJavaConfigurationShared + +extension Configuration { + public var effectiveUnsignedNumericsMode: UnsignedNumericsMode { + switch effectiveUnsignedNumbersMode { + case .annotate: .ignoreSign + case .wrapGuava: .wrapUnsignedGuava + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 28b3aba1..b8749299 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -105,9 +105,16 @@ extension FFMSwift2JavaGenerator { 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 name = param.name! // !-safe, because cdecl lowering guarantees the parameter named. + + let annotationsStr = + if param.type.javaType.parameterAnnotations.isEmpty { + "" + } else { + param.type.javaType.parameterAnnotations.map({$0.render()}).joined(separator: " ") + " " + } + params.append("\(annotationsStr)\(param.type.javaType) \(name)") + args.append(name) } let paramsStr = params.joined(separator: ", ") let argsStr = args.joined(separator: ", ") @@ -316,23 +323,21 @@ extension FFMSwift2JavaGenerator { let translatedSignature = translated.translatedSignature let returnTy = translatedSignature.result.javaResultType + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") + if !annotationsStr.isEmpty { annotationsStr += "\n" } + var paramDecls = translatedSignature.parameters .flatMap(\.javaParameters) - .map { "\($0.type) \($0.name)" } + .map { $0.renderParameter() } if translatedSignature.requiresSwiftArena { paramDecls.append("AllocatingSwiftArena swiftArena$") } // TODO: we could copy the Swift method's documentation over here, that'd be great UX + printDeclDocumentation(&printer, decl) printer.printBraceBlock( """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ - \(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) + \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) """ ) { printer in if case .instance(_) = decl.functionSignature.selfParameter { @@ -344,6 +349,19 @@ extension FFMSwift2JavaGenerator { } } + private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + printer.print( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + } + /// Print the actual downcall to the Swift API. /// /// This assumes that all the parameters are passed-in with appropriate names. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index b29ca5d9..f8b2e7e8 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension FFMSwift2JavaGenerator { func translatedDecl( @@ -24,7 +25,9 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) + let translation = JavaTranslation( + config: self.config, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -52,6 +55,9 @@ extension FFMSwift2JavaGenerator { /// Java type that represents the Swift result type. var javaResultType: JavaType + /// Java annotations that should be propagated from the result type onto the method + var annotations: [JavaAnnotation] = [] + /// Required indirect return receivers for receiving the result. /// /// 'JavaParameter.name' is the suffix for the receiver variable names. For example @@ -85,8 +91,13 @@ extension FFMSwift2JavaGenerator { /// Function signature. let translatedSignature: TranslatedFunctionSignature - /// Cdecl lowerd signature. + /// Cdecl lowered signature. let loweredSignature: LoweredFunctionSignature + + /// Annotations to include on the Java function declaration + var annotations: [JavaAnnotation] { + self.translatedSignature.annotations + } } /// Function signature for a Java API. @@ -94,6 +105,12 @@ extension FFMSwift2JavaGenerator { var selfParameter: TranslatedParameter? var parameters: [TranslatedParameter] var result: TranslatedResult + + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + var annotations: [JavaAnnotation] { + self.result.annotations + } } /// Represent a Swift closure type in the user facing Java API. @@ -113,15 +130,15 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { + let config: Configuration var knownTypes: SwiftKnownTypes - init(knownTypes: SwiftKnownTypes) { + init(config: Configuration, knownTypes: SwiftKnownTypes) { + self.config = config self.knownTypes = knownTypes } - func translate( - _ decl: ImportedFunc - ) throws -> TranslatedFunctionDecl { + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let lowering = CdeclLowering(knownTypes: knownTypes) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) @@ -303,6 +320,18 @@ extension FFMSwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { + // If the result type should cause any annotations on the method, include them here. + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) { + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: unsignedWrapperType, annotations: parameterAnnotations) + ], conversion: .call(.placeholder, function: "UnsignedNumbers.toPrimitive", withArena: false)) + } + } // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. @@ -311,8 +340,9 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: javaType - ) + name: parameterName, + type: javaType, + annotations: parameterAnnotations) ], conversion: .placeholder ) @@ -324,7 +354,10 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType")) + name: parameterName, + type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType"), + annotations: parameterAnnotations + ) ], conversion: .swiftValueSelfSegment(.placeholder) ) @@ -524,6 +557,37 @@ extension FFMSwift2JavaGenerator { } } + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, + mode: JExtractUnsignedIntegerMode) -> JavaConversionStep { + switch mode { + case .annotate: + return .placeholder // no conversions + + case .wrapGuava: + guard let typeName = javaType.fullyQualifiedClassName else { + fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") + } + + switch from { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint16: + return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java + case .uint32: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint64: + return .call(.placeholder, function: "\(typeName).fromLongBits", withArena: false) + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + } + } + /// Translate a Swift API result to the user-facing Java API result. func translate( swiftResult: SwiftResult, @@ -531,12 +595,29 @@ extension FFMSwift2JavaGenerator { ) throws -> TranslatedResult { let swiftType = swiftResult.type + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { + return TranslatedResult( + javaResultType: unsignedWrapperType, + outParameters: [], + conversion: unsignedResultConversion( + swiftType, to: unsignedWrapperType, + mode: self.config.effectiveUnsignedNumbersMode) + ) + } + } + + // If the result type should cause any annotations on the method, include them here. + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + // 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, + annotations: resultAnnotations, outParameters: [], conversion: .placeholder ) @@ -548,6 +629,7 @@ extension FFMSwift2JavaGenerator { let javaType = JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType") return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [], conversion: .construct(.placeholder, javaType) ) @@ -558,6 +640,7 @@ extension FFMSwift2JavaGenerator { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedResult( javaResultType: .javaForeignMemorySegment, + annotations: resultAnnotations, outParameters: [ JavaParameter(name: "pointer", type: .javaForeignMemorySegment), JavaParameter(name: "count", type: .long), @@ -597,6 +680,7 @@ extension FFMSwift2JavaGenerator { let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [ JavaParameter(name: "", type: javaType) ], @@ -702,7 +786,7 @@ extension CType { 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: 16)): return .char // char is Java's only unsigned primitive, we can use it! case .integral(.unsigned(bits: 32)): return .int case .integral(.unsigned(bits: 64)): return .long @@ -739,10 +823,10 @@ extension CType { 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 .integral(.unsigned(bits: 8)): return .SwiftUInt8 + case .integral(.unsigned(bits: 16)): return .SwiftUInt16 + case .integral(.unsigned(bits: 32)): return .SwiftUInt32 + case .integral(.unsigned(bits: 64)): return .SwiftUInt64 case .floating(.double): return .SwiftDouble case .floating(.float): return .SwiftFloat diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 1e32d1e5..f35973eb 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -38,7 +38,7 @@ extension FFMSwift2JavaGenerator { let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" let moduleFilename = "\(moduleFilenameBase).swift" do { - log.info("Printing contents: \(moduleFilename)") + log.debug("Printing contents: \(moduleFilename)") try printGlobalSwiftThunkSources(&printer) @@ -58,7 +58,7 @@ extension FFMSwift2JavaGenerator { 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)") + log.debug("Printing contents: \(filename)") do { try printSwiftThunkSources(&printer, ty: ty) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index d30d3e74..29962621 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -15,10 +15,12 @@ import JavaTypes import SwiftSyntax import SwiftSyntaxBuilder +import JavaKitConfigurationShared import struct Foundation.URL package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let log: Logger + let config: Configuration let analysis: AnalysisResult let swiftModuleName: String let javaPackage: String @@ -40,12 +42,14 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { var expectedOutputSwiftFiles: Set package init( + config: Configuration, translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String ) { self.log = Logger(label: "ffm-generator", logLevel: translator.log.logLevel) + self.config = config self.analysis = translator.result self.swiftModuleName = translator.swiftModuleName self.javaPackage = javaPackage @@ -96,7 +100,9 @@ extension FFMSwift2JavaGenerator { "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", "org.swift.swiftkit.ffm.*", - "org.swift.swiftkit.ffm.SwiftRuntime", + + // NonNull, Unsigned and friends + "org.swift.swiftkit.core.annotations.*", // Necessary for native calls and type mapping "java.lang.foreign.*", @@ -120,7 +126,7 @@ extension FFMSwift2JavaGenerator { 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)") + log.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) if let outputFile = try printer.writeContents( @@ -134,7 +140,7 @@ extension FFMSwift2JavaGenerator { do { let filename = "\(self.swiftModuleName).java" - log.info("Printing contents: \(filename)") + log.debug("Printing contents: \(filename)") printModule(&printer) if let outputFile = try printer.writeContents( @@ -178,7 +184,7 @@ extension FFMSwift2JavaGenerator { func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) - printImports(&printer) + printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl printNominal(&printer, decl) { printer in // We use a static field to abuse the initialization order such that by the time we get type metadata, diff --git a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift index 3784a75a..329efaad 100644 --- a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift +++ b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift @@ -67,11 +67,19 @@ extension ForeignValueLayout { public static let SwiftBool = Self(javaConstant: "SWIFT_BOOL") public static let SwiftInt = Self(javaConstant: "SWIFT_INT") + public static let SwiftUInt = Self(javaConstant: "SWIFT_UINT") + public static let SwiftInt64 = Self(javaConstant: "SWIFT_INT64") + public static let SwiftUInt64 = Self(javaConstant: "SWIFT_UINT64") + public static let SwiftInt32 = Self(javaConstant: "SWIFT_INT32") + public static let SwiftUInt32 = Self(javaConstant: "SWIFT_UINT32") + public static let SwiftInt16 = Self(javaConstant: "SWIFT_INT16") public static let SwiftUInt16 = Self(javaConstant: "SWIFT_UINT16") + public static let SwiftInt8 = Self(javaConstant: "SWIFT_INT8") + public static let SwiftUInt8 = Self(javaConstant: "SWIFT_UINT8") public static let SwiftFloat = Self(javaConstant: "SWIFT_FLOAT") public static let SwiftDouble = Self(javaConstant: "SWIFT_DOUBLE") diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index dd7e9c10..4c4d9c0f 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -78,6 +78,15 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } } + /// If this function type uses types that require any additional `import` statements, + /// these would be exported here. + var additionalJavaImports: Set { + var imports: Set = [] +// imports += self.functionSignature.parameters.flatMap { $0.additionalJavaImports } +// imports += self.functionSignature.result.additionalJavaImports + return imports + } + var isStatic: Bool { if case .staticMethod = functionSignature.selfParameter { return true diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift new file mode 100644 index 00000000..fe10ef72 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// 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 +import JavaKitConfigurationShared + +enum JNIJavaTypeTranslator { + + static func translate(knownType: SwiftKnownTypeDeclKind, config: Configuration) -> JavaType? { + let unsigned = config.effectiveUnsignedNumbersMode + guard unsigned == .annotate else { + // We do not support wrap mode in JNI mode currently; + // In the future this is where it would be interesting to implement Kotlin UInt support. + return nil + } + + switch knownType { + case .bool: return .boolean + + case .int8: return .byte + case .uint8: return .byte + + case .int16: return .short + case .uint16: return .char + + case .int32: return .int + case .uint32: return .int + + case .int64: return .long + case .uint64: return .long + + case .float: return .float + case .double: return .double + case .void: return .void + + case .string: return .javaLangString + case .int, .uint, // FIXME: why not supported int/uint? + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, + .optional, .data, .dataProtocol: + return nil + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index e0e5cb70..63b9dcd1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -20,6 +20,9 @@ extension JNISwift2JavaGenerator { static let defaultJavaImports: Array = [ "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", + + // NonNull, Unsigned and friends + "org.swift.swiftkit.core.annotations.*", ] } @@ -32,9 +35,11 @@ extension JNISwift2JavaGenerator { } package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { - for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) + + for (_, ty) in importedTypes { let filename = "\(ty.swiftNominal.name).java" - logger.info("Printing contents: \(filename)") + logger.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) if let outputFile = try printer.writeContents( @@ -62,6 +67,7 @@ extension JNISwift2JavaGenerator { private func printModule(_ printer: inout CodePrinter) { printHeader(&printer) printPackage(&printer) + printImports(&printer) printModuleClass(&printer) { printer in printer.print( @@ -81,6 +87,7 @@ extension JNISwift2JavaGenerator { } for decl in analysis.importedGlobalVariables { + self.logger.trace("Print global variable: \(decl)") printFunctionDowncallMethods(&printer, decl) printer.println() } @@ -219,7 +226,7 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ functionType: TranslatedFunctionType ) { - let apiParams = functionType.parameters.map(\.parameter.asParameter) + let apiParams = functionType.parameters.map({ $0.parameter.renderParameter() }) printer.print( """ @@ -243,18 +250,21 @@ extension JNISwift2JavaGenerator { let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.asParameter) + var parameters = translatedDecl.translatedFunctionSignature.parameters.map({ $0.parameter.renderParameter() }) if translatedSignature.requiresSwiftArena { parameters.append("SwiftArena swiftArena$") } let throwsClause = decl.isThrowing ? " throws Exception" : "" + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") + if !annotationsStr.isEmpty { annotationsStr += "\n" } + let modifiersStr = modifiers.joined(separator: " ") let parametersStr = parameters.joined(separator: ", ") printDeclDocumentation(&printer, decl) printer.printBraceBlock( - "\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + "\(annotationsStr)\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" ) { printer in printDowncall(&printer, decl) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 8d18e6ae..5908cfb1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension JNISwift2JavaGenerator { func translatedDecl( @@ -25,6 +26,7 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { let translation = JavaTranslation( + config: config, swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable @@ -40,12 +42,14 @@ extension JNISwift2JavaGenerator { } struct JavaTranslation { + let config: Configuration let swiftModuleName: String let javaPackage: String let javaClassLookupTable: JavaClassLookupTable func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let nativeTranslation = NativeJavaTranslation( + config: self.config, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable ) @@ -117,12 +121,12 @@ extension JNISwift2JavaGenerator { ) } - let transltedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) + let translatedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) return TranslatedFunctionType( name: name, parameters: translatedParams, - result: transltedResult, + result: translatedResult, swiftType: swiftType ) } @@ -150,10 +154,12 @@ extension JNISwift2JavaGenerator { selfParameter = nil } - return try TranslatedFunctionSignature( + let resultType = try translate(swiftResult: functionSignature.result) + + return TranslatedFunctionSignature( selfParameter: selfParameter, parameters: parameters, - resultType: translate(swiftResult: functionSignature.result) + resultType: resultType ) } @@ -163,17 +169,33 @@ extension JNISwift2JavaGenerator { methodName: String, parentName: String ) throws -> TranslatedParameter { + + // If the result type should cause any annotations on the method, include them here. + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) { + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: unsignedWrapperType, annotations: parameterAnnotations), + conversion: unsignedResultConversion( + swiftType, to: unsignedWrapperType, + mode: self.config.effectiveUnsignedNumbersMode) + ) + } + } + switch swiftType { case .nominal(let nominalType): let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: javaType), + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), conversion: .placeholder ) } @@ -184,10 +206,7 @@ extension JNISwift2JavaGenerator { } return TranslatedParameter( - parameter: JavaParameter( - name: parameterName, - type: javaType - ), + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), conversion: .placeholder ) } @@ -196,14 +215,15 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: nominalTypeName) + type: .class(package: nil, name: nominalTypeName), + annotations: parameterAnnotations ), conversion: .valueMemoryAddress(.placeholder) ) case .tuple([]): return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: .void), + parameter: JavaParameter(name: parameterName, type: .void, annotations: parameterAnnotations), conversion: .placeholder ) @@ -211,7 +231,8 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)") + type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + annotations: parameterAnnotations ), conversion: .placeholder ) @@ -221,30 +242,46 @@ extension JNISwift2JavaGenerator { } } - func translate( - swiftResult: SwiftResult - ) throws -> TranslatedResult { - switch swiftResult.type { + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, + mode: JExtractUnsignedIntegerMode) -> JavaNativeConversionStep { + switch mode { + case .annotate: + return .placeholder // no conversions + + case .wrapGuava: + fatalError("JExtract in JNI mode does not support the \(JExtractUnsignedIntegerMode.wrapGuava) unsigned numerics mode") + } + } + + func translate(swiftResult: SwiftResult) throws -> TranslatedResult { + let swiftType = swiftResult.type + + // If the result type should cause any annotations on the method, include them here. + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) } return TranslatedResult( javaType: javaType, + annotations: resultAnnotations, conversion: .placeholder ) } if nominalType.isJavaKitWrapper { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + throw JavaTranslationError.unsupportedSwiftType(swiftType) } // We assume this is a JExtract class. let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) return TranslatedResult( javaType: javaType, + annotations: resultAnnotations, conversion: .constructSwiftValue(.placeholder, javaType) ) @@ -252,7 +289,7 @@ extension JNISwift2JavaGenerator { return TranslatedResult(javaType: .void, conversion: .placeholder) case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + throw JavaTranslationError.unsupportedSwiftType(swiftType) } } } @@ -275,33 +312,23 @@ extension JNISwift2JavaGenerator { /// Function signature of the native function that will be implemented by Swift let nativeFunctionSignature: NativeFunctionSignature - } - static func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { - switch knownType { - case .bool: .boolean - 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 .string: .javaLangString - case .int, .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: - nil + /// Annotations to include on the Java function declaration + var annotations: [JavaAnnotation] { + self.translatedFunctionSignature.annotations } } struct TranslatedFunctionSignature { - let selfParameter: TranslatedParameter? - let parameters: [TranslatedParameter] - let resultType: TranslatedResult + var selfParameter: TranslatedParameter? + var parameters: [TranslatedParameter] + var resultType: TranslatedResult + + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + var annotations: [JavaAnnotation] { + self.resultType.annotations + } var requiresSwiftArena: Bool { return self.resultType.conversion.requiresSwiftArena @@ -318,6 +345,9 @@ extension JNISwift2JavaGenerator { struct TranslatedResult { let javaType: JavaType + /// Java annotations that should be propagated from the result type onto the method + var annotations: [JavaAnnotation] = [] + /// Represents how to convert the Java native result into a user-facing result. let conversion: JavaNativeConversionStep } @@ -343,6 +373,8 @@ extension JNISwift2JavaGenerator { /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + indirect case call(JavaNativeConversionStep, function: String) + /// 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. @@ -350,14 +382,18 @@ extension JNISwift2JavaGenerator { switch self { case .placeholder: return placeholder - + case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" - + case .constructSwiftValue(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" - + + case .call(let inner, let function): + let inner = inner.render(&printer, placeholder) + return "\(function)(\(inner))" + } } @@ -372,12 +408,18 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + + case .call(let inner, _): + return inner.requiresSwiftArena } } } enum JavaTranslationError: Error { - case unsupportedSwiftType(SwiftType) + case unsupportedSwiftType(SwiftType, fileID: String, line: Int) + static func unsupportedSwiftType(_ type: SwiftType, _fileID: String = #fileID, _line: Int = #line) -> JavaTranslationError { + .unsupportedSwiftType(type, fileID: _fileID, line: _line) + } /// The user has not supplied a mapping from `SwiftType` to /// a java class. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 48a6e8d4..9f1113fc 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -13,10 +13,12 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension JNISwift2JavaGenerator { struct NativeJavaTranslation { + let config: Configuration let javaPackage: String let javaClassLookupTable: JavaClassLookupTable @@ -70,7 +72,8 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } @@ -140,7 +143,8 @@ extension JNISwift2JavaGenerator { switch type { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(type) } @@ -172,7 +176,8 @@ extension JNISwift2JavaGenerator { switch type { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(type) } @@ -198,7 +203,10 @@ extension JNISwift2JavaGenerator { switch swiftResult.type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + guard javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 35d4dbef..8b910ffb 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -59,7 +59,7 @@ extension JNISwift2JavaGenerator { 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" - logger.info("Printing contents: \(filename)") + logger.debug("Printing contents: \(filename)") do { try printNominalTypeThunks(&printer, ty) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 79f546ac..c5f3a5b6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -13,16 +13,19 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared /// A table that where keys are Swift class names and the values are /// the fully qualified canoical names. package typealias JavaClassLookupTable = [String: String] package class JNISwift2JavaGenerator: Swift2JavaGenerator { + + let logger: Logger + let config: Configuration let analysis: AnalysisResult let swiftModuleName: String let javaPackage: String - let logger: Logger let swiftOutputDirectory: String let javaOutputDirectory: String @@ -42,12 +45,14 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var expectedOutputSwiftFiles: Set package init( + config: Configuration, translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String, javaClassLookupTable: JavaClassLookupTable ) { + self.config = config self.logger = Logger(label: "jni-generator", logLevel: translator.log.logLevel) self.analysis = translator.result self.swiftModuleName = translator.swiftModuleName diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift index feb8a545..cdedb0a1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIType.swift @@ -88,7 +88,7 @@ extension JavaType { /// Returns whether this type returns `JavaValue` from JavaKit var implementsJavaValue: Bool { - switch self { + return switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: true default: diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 72896419..a12b13b2 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -18,8 +18,21 @@ import JavaTypes struct JavaParameter { let name: String let type: JavaType + /// Parameter annotations are used in parameter declarations like this: `@Annotation int example` + let annotations: [JavaAnnotation] - var asParameter: String { - "\(type) \(name)" + init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { + self.name = name + self.type = type + self.annotations = annotations + } + + func renderParameter() -> String { + if annotations.isEmpty { + return "\(type) \(name)" + } + + let annotationsStr = annotations.map({$0.render()}).joined(separator: "") + return "\(annotationsStr) \(type) \(name)" } } diff --git a/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift similarity index 100% rename from Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift rename to Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift new file mode 100644 index 00000000..2ab9c0a2 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +extension JavaType { + + /// Try to map a Swift type name (e.g., from the module Swift) over to a + /// primitive Java type, or fail otherwise. + public init?(swiftTypeName: String, WHT_unsigned unsigned: UnsignedNumericsMode) { + switch swiftTypeName { + case "Bool": self = .boolean + + case "Int8": self = .byte + case "UInt8": + self = switch unsigned { + case .ignoreSign: .byte + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedInteger + } + + case "Int16": self = .short + case "UInt16": self = .char + + case "Int32": self = .int + case "UInt32": + self = switch unsigned { + case .ignoreSign: .int + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedInteger + } + + case "Int64": self = .long + case "UInt64": + self = switch unsigned { + case .ignoreSign: .long + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedLong + } + + case "Float": self = .float + case "Double": self = .double + case "Void": self = .void + default: return nil + } + } +} + +extension JavaType { + + static func unsignedWrapper(for swiftType: SwiftType) -> JavaType? { + switch swiftType { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: return guava.primitives.UnsignedInteger + case .uint16: return .char // no wrapper necessary, we can express it as 'char' natively in Java + case .uint32: return guava.primitives.UnsignedInteger + case .uint64: return guava.primitives.UnsignedLong + default: return nil + } + default: return nil + } + } + + /// Known types from the Google Guava library + enum guava { + enum primitives { + static let package = "com.google.common.primitives" + + static var UnsignedInteger: JavaType { + .class(package: primitives.package, name: "UnsignedInteger") + } + + static var UnsignedLong: JavaType { + .class(package: primitives.package, name: "UnsignedLong") + } + } + } + +} diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 6473cea3..5a73e328 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -98,6 +98,7 @@ public struct SwiftToJava { switch config.mode { case .some(.ffm), .none: let generator = FFMSwift2JavaGenerator( + config: self.config, translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, @@ -108,6 +109,7 @@ public struct SwiftToJava { case .jni: let generator = JNISwift2JavaGenerator( + config: self.config, translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index da738d39..3cc14406 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -92,6 +92,17 @@ enum SwiftType: Equatable { return false } } + + var isUnsignedInteger: Bool { + switch self { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8, .uint16, .uint32, .uint64: true + default: false + } + default: false + } + } } extension SwiftType: CustomStringConvertible { diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift index 1af1b549..005753eb 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift @@ -14,6 +14,66 @@ import JavaTypes +extension UInt8: JavaValue { + public typealias JNIType = jbyte + + public static var jvalueKeyPath: WritableKeyPath { \.b } + + public static var javaType: JavaType { .byte } + + /// Retrieve the JNI value. + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + /// Initialize from a JNI value. + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Self(value) + } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallByteMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetByteField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetByteField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticByteMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticByteField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticByteField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewByteArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetByteArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetByteArrayRegion + } + + public static var jniPlaceholderValue: jbyte { + 0 + } +} + extension Int8: JavaValue { public typealias JNIType = jbyte @@ -170,6 +230,66 @@ extension Int16: JavaValue { } } +extension UInt32: JavaValue { + public typealias JNIType = jint + + public static var jvalueKeyPath: WritableKeyPath { \.i } + + public static var javaType: JavaType { .int } + + /// Retrieve the JNI value. + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + /// Initialize from a JNI value. + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Self(value) + } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallIntMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetIntField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetIntField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticIntMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticIntField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticIntField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewIntArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetIntArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetIntArrayRegion + } + + public static var jniPlaceholderValue: jint { + 0 + } +} + extension Int32: JavaValue { public typealias JNIType = jint @@ -228,6 +348,64 @@ extension Int32: JavaValue { } } +extension UInt64: JavaValue { + public typealias JNIType = jlong + + public static var jvalueKeyPath: WritableKeyPath { \.j } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = UInt64(value) + } + + public static var javaType: JavaType { .long } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallLongMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetLongField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetLongField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticLongMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticLongField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticLongField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewLongArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetLongArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetLongArrayRegion + } + + public static var jniPlaceholderValue: jlong { + 0 + } +} + extension Int64: JavaValue { public typealias JNIType = jlong diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 5c57082e..c1ae7dd2 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -42,6 +42,11 @@ public struct Configuration: Codable { public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable + public var unsignedNumbersMode: JExtractUnsignedIntegerMode? + public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { + unsignedNumbersMode ?? .default + } + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index b4a96476..190323a0 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. public enum JExtractGenerationMode: String, Codable { /// Foreign Value and Memory API case ffm @@ -19,3 +20,46 @@ public enum JExtractGenerationMode: String, Codable { /// Java Native Interface case jni } + +/// Configures how Swift unsigned integers should be extracted by jextract. +public enum JExtractUnsignedIntegerMode: String, Codable { + /// Treat unsigned Swift integers as their signed equivalents in Java signatures, + /// however annotate them using the `@Unsigned` annotation which serves as a hint + /// to users of APIs with unsigned integers that a given parameter or return type + /// is actually unsigned, and must be treated carefully. + /// + /// Specifically negative values of a `@Unchecked long` must be interpreted carefully as + /// a value larger than the Long.MAX_VALUE can represent in Java. + case annotate + + /// Wrap any unsigned Swift integer values in an explicit `Unsigned...` wrapper types. + /// + /// This mode trades off performance, due to needing to allocate the type-safe wrapper objects around + /// primitive values, however allows to retain static type information about the unsignedness of + /// unsigned number types in the Java side of generated bindings. + case wrapGuava + +// /// If possible, use a wider Java signed integer type to represent an Unsigned Swift integer type. +// /// For example, represent a Swift `UInt32` (width equivalent to Java `int`) as a Java signed `long`, +// /// because UInt32's max value is possible to be stored in a signed Java long (64bit). +// /// +// /// Since it is not possible to widen a value beyond 64bits (Java `long`), the Long type would be wrapped +// case widenOrWrap +// +// /// Similar to `widenOrWrap`, however instead of wrapping `UInt64` as an `UnsignedLong` in Java, +// /// only annotate it as `@Unsigned long`. +// case widenOrAnnotate +} + +extension JExtractUnsignedIntegerMode { + public var needsConversion: Bool { + switch self { + case .annotate: false + case .wrapGuava: true + } + } + + public static var `default`: JExtractUnsignedIntegerMode { + .annotate + } +} diff --git a/Sources/JavaTypes/JavaAnnotation.swift b/Sources/JavaTypes/JavaAnnotation.swift new file mode 100644 index 00000000..a643c298 --- /dev/null +++ b/Sources/JavaTypes/JavaAnnotation.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a Java annotation (e.g. `@Deprecated` or `@Unsigned`) +public struct JavaAnnotation: Equatable, Hashable { + public let type: JavaType + public let arguments: [String] + + public init(className name: some StringProtocol, arguments: [String] = []) { + type = JavaType(className: name) + self.arguments = arguments + } + + public func render() -> String { + guard let className = type.className else { + fatalError("Java annotation must have a className") + } + + var res = "@\(className)" + guard !arguments.isEmpty else { + return res + } + + res += "(" + res += arguments.joined(separator: ",") + res += ")" + return res + } + +} + +extension JavaAnnotation { + public static var unsigned: JavaAnnotation { + JavaAnnotation(className: "Unsigned") + } +} \ No newline at end of file diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index ccb4e96b..f9e2e0cd 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -60,7 +60,7 @@ extension JavaType: CustomStringConvertible { } } - /// Returns the a class name if this java type was a class, + /// Returns the class name if this java type was a class, /// and nil otherwise. public var className: String? { switch self { @@ -70,4 +70,17 @@ extension JavaType: CustomStringConvertible { return nil } } + + /// Returns the fully qualified class name if this java type was a class, + /// and nil otherwise. + public var fullyQualifiedClassName: String? { + switch self { + case .class(.some(let package), let name): + return "\(package).\(name)" + case .class(nil, let name): + return name + default: + return nil + } + } } diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 492ff459..20de73fc 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -85,20 +85,32 @@ extension JavaType { } } - /// Try to map a Swift type name (e.g., from the module Swift) over to a - /// primitive Java type, or fail otherwise. - public init?(swiftTypeName: String) { - switch swiftTypeName { - case "Bool": self = .boolean - case "Int8": self = .byte - case "UInt16": self = .char - case "Int16": self = .short - case "Int32": self = .int - case "Int64": self = .long - case "Float": self = .float - case "Double": self = .double - case "Void": self = .void - default: return nil - } - } +} + +/// Determines how type conversion should deal with Swift's unsigned numeric types. +/// +/// When `ignoreSign` is used, unsigned Swift types are imported directly as their corresponding bit-width types, +/// which may yield surprising values when an unsigned Swift value is interpreted as a signed Java type: +/// - `UInt8` is imported as `byte` +/// - `UInt16` is imported as `char` (this is always correct, since `char` is unsigned in Java) +/// - `UInt32` is imported as `int` +/// - `UInt64` is imported as `long` +/// +/// When `wrapUnsignedGuava` is used, unsigned Swift types are imported as safe "wrapper" types from the popular Guava +/// library on the Java side. SwiftJava does not include these types, so you would have to make sure your project depends +/// on Guava for such generated code to be able to compile. +/// +/// These make the Unsigned nature of the types explicit in Java, however they come at a cost of allocating the wrapper +/// object, and indirection when accessing the underlying numeric value. These are often useful as a signal to watch out +/// when dealing with a specific API, however in high performance use-cases, one may want to choose using the primitive +/// values directly, and interact with them using {@code UnsignedIntegers} SwiftKit helper classes on the Java side. +/// +/// The type mappings in this mode are as follows: +/// - `UInt8` is imported as `com.google.common.primitives.UnsignedInteger` +/// - `UInt16` is imported as `char` (this is always correct, since `char` is unsigned in Java) +/// - `UInt32` is imported as `com.google.common.primitives.UnsignedInteger` +/// - `UInt64` is imported as `com.google.common.primitives.UnsignedLong` +public enum UnsignedNumericsMode { + case ignoreSign + case wrapUnsignedGuava } diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 6c5f5357..2a2d901f 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -13,13 +13,15 @@ //===----------------------------------------------------------------------===// /// Describes the Java type system. +/// +/// Some types may need to be annotated when in parameter position, public enum JavaType: Equatable, Hashable { case boolean - case byte - case char - case short - case int - case long + case byte(parameterAnnotations: [JavaAnnotation]) + case char(parameterAnnotations: [JavaAnnotation]) + case short(parameterAnnotations: [JavaAnnotation]) + case int(parameterAnnotations: [JavaAnnotation]) + case long(parameterAnnotations: [JavaAnnotation]) case float case double case void @@ -31,6 +33,12 @@ public enum JavaType: Equatable, Hashable { /// A Java array. indirect case array(JavaType) + public static var byte: JavaType { .byte(parameterAnnotations: []) } + public static var char: JavaType { .char(parameterAnnotations: []) } + public static var short: JavaType { .short(parameterAnnotations: []) } + public static var int: JavaType { .int(parameterAnnotations: []) } + public static var long: JavaType { .long(parameterAnnotations: []) } + /// Given a class name such as "java.lang.Object", split it into /// its package and class name to form a class instance. public init(className name: some StringProtocol) { @@ -45,6 +53,21 @@ public enum JavaType: Equatable, Hashable { } } +extension JavaType { + /// List of Java annotations this type should have include in parameter position, + /// e.g. `void example(@Unsigned long num)` + public var parameterAnnotations: [JavaAnnotation] { + switch self { + case .byte(let parameterAnnotations): return parameterAnnotations + case .char(let parameterAnnotations): return parameterAnnotations + case .short(let parameterAnnotations): return parameterAnnotations + case .int(let parameterAnnotations): return parameterAnnotations + case .long(let parameterAnnotations): return parameterAnnotations + default: return [] + } + } +} + extension JavaType { /// Whether this is a primitive Java type. public var isPrimitive: Bool { @@ -57,3 +80,4 @@ extension JavaType { } } } + diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index e7acb3eb..b3d02276 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -45,7 +45,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Swift Feature | FFM | JNI | -|--------------------------------------------------------------------------------------| -------- |-----| +|--------------------------------------------------------------------------------------|----------|-----| | Initializers: `class`, `struct` | ✅ | ✅ | | Optional Initializers / Throwing Initializers | ❌ | ❌ | | Deinitializers: `class`, `struct` | ✅ | ✅ | @@ -67,7 +67,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | | Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | | Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | -| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | +| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ✅ * | ✅ * | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | | Parametrer packs / Variadic generics | ❌ | ❌ | @@ -76,14 +76,14 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Operators: `+`, `-`, user defined | ❌ | ❌ | | Subscripts: `subscript()` | ❌ | ❌ | | Equatable | ❌ | ❌ | -| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | +| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | | Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | | Inheritance: `class Caplin: Capybara` | ❌ | ❌ | | Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | | Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | | Non-escaping closures with object arguments/results: `func callMe(maybe: (JavaObj) -> (JavaObj))` | ❌ | ❌ | | `@escaping` closures: `func callMe(_: @escaping () -> ())` | ❌ | ❌ | -| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | +| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | | Swift macros (maybe) | ❌ | ❌ | | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | @@ -94,3 +94,65 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | | | | > tip: The list of features may be incomplete, please file an issue if something is unclear or should be clarified in this table. + +## Detailed feature support discussion + +### Unsigned integers + +### Java <-> Swift Type mapping + +Java does not support unsigned numbers (other than the 16-bit wide `char`), and therefore mapping Swift's (and C) +unsigned integer types is somewhat problematic. + +SwiftJava's jextract mode, similar to OpenJDK jextract, does extract unsigned types from native code to Java +as their bit-width equivalents. This is potentially dangerous because values larger than the `MAX_VALUE` of a given +*signed* type in Java, e.g. `200` stored in an `UInt8` in Swift, would be interpreted as a `byte` of value `-56`, +because Java's `byte` type is _signed_. + +#### Unsigned numbers mode: annotate (default) + +Because in many situations the data represented by such numbers is merely passed along, and not interpreted by Java, +this may be safe to pass along. However, interpreting unsigned values incorrectly like this can lead to subtle mistakes +on the Java side. + +| Swift type | Java type | +|------------|-----------| +| `Int8` | `byte` | +| `UInt8` | `byte` ⚠️ | +| `Int16` | `short` | +| `UInt16` | `char` | +| `Int32` | `int` | +| `UInt32` | `int` ⚠️ | +| `Int64` | `long` | +| `UInt64` | `long` ⚠️ | +| `Float` | `float` | +| `Double` | `double` | + +#### Unsigned numbers mode: wrap-guava + +You can configure `jextract` (in FFM mode) to instead import unsigned values as their unsigned type-safe representations +as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers wrap-guava` +command line option, or set the corresponding configuration value in `swift-java.config` (TODO). + +This approach is type-safe, however it incurs a performance penalty for allocating a wrapper class for every +unsigned integer parameter passed to and from native Swift functions. + +SwiftJava _does not_ vendor or provide the Guava library as a dependency, and when using this mode +you are expected to add a Guava dependency to your Java project. + +> You can read more about the unsigned integers support + +| Swift type | Java type | +|------------|--------------------------------------------------------| +| `Int8` | `byte` | +| `UInt8` | `com.google.common.primitives.UnsignedInteger` (class) | +| `Int16` | `short` | +| `UInt16` | `char` | +| `Int32` | `int` | +| `UInt32` | `com.google.common.primitives.UnsignedInteger` (class)️ | +| `Int64` | `long` | +| `UInt64` | `com.google.common.primitives.UnsignedLong` (class) | +| `Float` | `float` | +| `Double` | `double` | + +> Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. diff --git a/Sources/SwiftJavaLib/JavaClassTranslator.swift b/Sources/SwiftJavaLib/JavaClassTranslator.swift index bfd1657f..ea6ca481 100644 --- a/Sources/SwiftJavaLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaLib/JavaClassTranslator.swift @@ -517,7 +517,7 @@ extension JavaClassTranslator { package func renderConstructor( _ javaConstructor: Constructor ) throws -> DeclSyntax { - let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] + let parameters = try translateJavaParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] let parametersStr = parameters.map { $0.description }.joined(separator: ", ") let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" @@ -537,7 +537,7 @@ extension JavaClassTranslator { whereClause: String = "" ) throws -> DeclSyntax { // Map the parameters. - let parameters = try translateParameters(javaMethod.getParameters()) + let parameters = try translateJavaParameters(javaMethod.getParameters()) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") @@ -700,7 +700,7 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + private func translateJavaParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { return try parameters.compactMap { javaParameter in guard let javaParameter else { return nil } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index e4901bc2..03ca0cd7 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,6 +61,9 @@ extension SwiftJava { @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 + @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") + var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, @@ -81,6 +84,12 @@ extension SwiftJava.JExtractCommand { config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift config.writeEmptyFiles = writeEmptyFiles + config.unsignedNumbersMode = unsignedNumbers + + guard checkModeCompatibility() else { + // check would have logged the reason for early exit. + return + } if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -97,6 +106,28 @@ extension SwiftJava.JExtractCommand { try jextractSwift(config: config, dependentConfigs: dependentConfigs.map(\.1)) } + + /// Check if the configured modes are compatible, and fail if not + func checkModeCompatibility() -> Bool { + if self.mode == .jni { + switch self.unsignedNumbers { + case .annotate: + print("Error: JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") + return false + case .wrapGuava: + () // OK + } + } + + return true + } +} + +struct IncompatibleModeError: Error { + let message: String + init(_ message: String) { + self.message = message + } } extension SwiftJava.JExtractCommand { @@ -110,3 +141,4 @@ extension SwiftJava.JExtractCommand { } extension JExtractGenerationMode: ExpressibleByArgument {} +extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 4f022d71..3eb01fe7 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -148,7 +148,7 @@ extension SwiftJava.ResolveCommand { } 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 ?? "")>>>") + "Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "")>>>") } return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) diff --git a/Sources/_Subprocess/Configuration.swift b/Sources/_Subprocess/Configuration.swift index 4dad1a47..ba6f15ba 100644 --- a/Sources/_Subprocess/Configuration.swift +++ b/Sources/_Subprocess/Configuration.swift @@ -40,7 +40,7 @@ public struct Configuration: Sendable { public var environment: Environment /// The working directory to use when running the executable. public var workingDirectory: FilePath - /// The platform specifc options to use when + /// The platform specific options to use when /// running the subprocess. public var platformOptions: PlatformOptions diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 7bab76e0..82874e20 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -14,6 +14,7 @@ plugins { id("build-logic.java-application-conventions") + id("me.champeau.jmh") version "0.7.2" id("maven-publish") } @@ -45,12 +46,21 @@ java { languageVersion.set(JavaLanguageVersion.of(17)) } // Support Android 6+ (Java 7) - sourceCompatibility = JavaVersion.VERSION_1_7 - targetCompatibility = JavaVersion.VERSION_1_7 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { - testImplementation 'junit:junit:4.13.2' + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +testing { + suites { + test { + useJUnitJupiter('5.10.3') + } + } } tasks.test { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java new file mode 100644 index 00000000..6956eaca --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +public class NotImplementedError extends AssertionError { + + private static final long serialVersionUID = 1L; + + public NotImplementedError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java new file mode 100644 index 00000000..bd1e52b0 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java @@ -0,0 +1,153 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/* + * Copyright (C) 2007 The Guava Authors + * + * 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. + */ + +package org.swift.swiftkit.core; + +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Collection of convenience functions to check argument preconditions. + *

+ * Partially based on {@code com.google.common.base.Preconditions}. + */ +public final class Preconditions { + private Preconditions() { + } + + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + public static void checkArgument(boolean expression, @Nullable String format) { + if (!expression) { + throw new IllegalArgumentException(format); + } + } + + public static void checkArgument(boolean expression, @Nullable String format, + @Nullable Object arg1) { + if (!expression) { + throw new IllegalArgumentException(String.format(format, arg1)); + } + } + + public static void checkArgument(boolean expression, @Nullable String format, + @Nullable Object arg1, + @Nullable Object arg2) { + if (!expression) { + throw new IllegalArgumentException(String.format(format, arg1, arg2)); + } + } + + public static T checkNotNull(@Nullable T reference) { + if (reference == null) { + throw new NullPointerException(); + } + + return reference; + } + + public static T checkNotNull(@Nullable T reference, @Nullable String message) { + if (reference == null) { + throw new NullPointerException(message); + } + + return reference; + } + + /* + * All recent hotspots (as of 2009) *really* like to have the natural code + * + * if (guardExpression) { + * throw new BadException(messageExpression); + * } + * + * refactored so that messageExpression is moved to a separate String-returning method. + * + * if (guardExpression) { + * throw new BadException(badMsg(...)); + * } + * + * The alternative natural refactorings into void or Exception-returning methods are much slower. + * This is a big deal - we're talking factors of 2-8 in microbenchmarks, not just 10-20%. (This is + * a hotspot optimizer bug, which should be fixed, but that's a separate, big project). + * + * The coding pattern above is heavily used in java.util, e.g. in ArrayList. There is a + * RangeCheckMicroBenchmark in the JDK that was used to test this. + * + * But the methods in this class want to throw different exceptions, depending on the args, so it + * appears that this pattern is not directly applicable. But we can use the ridiculous, devious + * trick of throwing an exception in the middle of the construction of another exception. Hotspot + * is fine with that. + */ + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException( + String.format("%s, index:%d, size:%d", desc, index, size)); + } + return index; + } + + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException( + String.format("Start index:%d, end index:%d, size: %d", start, end, size)); + } + } + +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java new file mode 100644 index 00000000..cad6cd8b --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +// TODO: Consider depending on jspecify instead +@Documented +@Target(TYPE_USE) +@Retention(RUNTIME) +public @interface NonNull {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java new file mode 100644 index 00000000..c20ad884 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +// TODO: Consider depending on jspecify instead +@Documented +@Target(TYPE_USE) +@Retention(RUNTIME) +public @interface Nullable {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java new file mode 100644 index 00000000..4bf8e354 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import jdk.jfr.Description; +import jdk.jfr.Label; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Value is of an unsigned numeric type. + *

+ * This annotation is used to annotate java integer primitives when their + * corresponding Swift type was actually unsigned, e.g. an {@code @Unsigned long} + * in a method signature corresponds to a Swift {@code UInt64} type, and therefore + * negative values reported by the signed {@code long} should instead be interpreted positive values, + * larger than {@code Long.MAX_VALUE} that are just not representable using a signed {@code long}. + */ +@Documented +@Label("Unsigned integer type") +@Description("Value should be interpreted as unsigned data type") +@Target({TYPE_USE, PARAMETER, FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Unsigned { +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java index c0a75144..be883e05 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java @@ -34,11 +34,19 @@ public static long addressByteSize() { } 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.OfByte SWIFT_UINT8 = SWIFT_INT8; + public static final ValueLayout.OfShort SWIFT_INT16 = ValueLayout.JAVA_SHORT; + public static final ValueLayout.OfChar SWIFT_UINT16 = ValueLayout.JAVA_CHAR; + public static final ValueLayout.OfInt SWIFT_INT32 = ValueLayout.JAVA_INT; + public static final ValueLayout.OfInt SWIFT_UINT32 = SWIFT_INT32; + public static final ValueLayout.OfLong SWIFT_INT64 = ValueLayout.JAVA_LONG; + public static final ValueLayout.OfLong SWIFT_UINT64 = SWIFT_INT64; + public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT; public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE; diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index eebfdf4a..0b8ca1d3 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -43,6 +43,7 @@ func assertLoweredFunction( translator.prepareForTranslation() let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -128,6 +129,7 @@ func assertLoweredVariableAccessor( translator.prepareForTranslation() let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: javaPackage, swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index ffefe907..e975d223 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -25,6 +25,7 @@ enum RenderKind { func assertOutput( dump: Bool = false, input: String, + config: Configuration? = nil, _ mode: JExtractGenerationMode, _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", @@ -36,8 +37,7 @@ func assertOutput( line: Int = #line, column: Int = #column ) throws { - var config = Configuration() - config.logLevel = .trace + var config = config ?? Configuration() config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) @@ -49,6 +49,7 @@ func assertOutput( switch mode { case .ffm: let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -64,6 +65,7 @@ func assertOutput( case .jni: let generator = JNISwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 1678f91f..a87294b0 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -47,6 +47,7 @@ final class FuncCallbackImportTests { let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -129,13 +130,13 @@ final class FuncCallbackImportTests { var config = Configuration() config.swiftModule = "__FakeModule" let st = Swift2JavaTranslator(config: config) - 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( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -251,6 +252,7 @@ final class FuncCallbackImportTests { let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 6854b260..a8d83a2a 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -246,6 +246,7 @@ extension FunctionDescriptorTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: javaPackage, swiftOutputDirectory: "/fake", @@ -275,6 +276,7 @@ extension FunctionDescriptorTests { try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: javaPackage, swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index cfe78fbd..483d53f5 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -18,78 +18,92 @@ import Testing @Suite struct JNIClassTests { let source = """ - public class MyClass { - let x: Int64 - let y: Int64 - - public static func method() {} - - public init(x: Int64, y: Int64) { - self.x = y - self.y = y - } - - public init() { - self.x = 0 - self.y = 0 + public class MyClass { + let x: Int64 + let y: Int64 + + public static func method() {} + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public init() { + self.x = 0 + self.y = 0 + } + + public func doSomething(x: Int64) {} + + public func copy() -> MyClass {} + public func isEqual(to other: MyClass) -> Bool {} } - - public func doSomething(x: Int64) {} - - public func copy() -> MyClass {} - public func isEqual(to other: MyClass) -> Bool {} - } - """ + """ @Test func generatesJavaClass() throws { - try assertOutput(input: source, .jni, .java, expectedChunks: [ - """ - // Generated by jextract-swift - // Swift module: SwiftModule + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule - package com.example.swift; - - import org.swift.swiftkit.core.*; - import org.swift.swiftkit.core.util.*; + package com.example.swift; - public final class MyClass extends JNISwiftInstance { - static final String LIB_NAME = "SwiftModule"; - - @SuppressWarnings("unused") - private static final boolean INITIALIZED_LIBS = initializeLibs(); - static boolean initializeLibs() { - System.loadLibrary(LIB_NAME); - return true; - } - - public MyClass(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } - """, - """ - private static native void $destroy(long selfPointer); - """, - """ - @Override - protected Runnable $createDestroyFunction() { - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyClass.$createDestroyFunction", - "this", this, - "self", self$); - } - return new Runnable() { - @Override - public void run() { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyClass.$destroy", "self", self$); - } - MyClass.$destroy(self$); + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """, + """ + public final class MyClass extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + public MyClass(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + ]) + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + private static native void $destroy(long selfPointer); + """ + ]) + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyClass.$createDestroyFunction", + "this", this, + "self", self$); } - }; - """ - ]) + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyClass.$destroy", "self", self$); + } + MyClass.$destroy(self$); + } + }; + """ + ]) } @Test @@ -101,18 +115,18 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public static func method() - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public static func method() + * } + */ public static void method() { MyClass.$method(); } """, """ private static native void $method(); - """ + """, ] ) } @@ -169,7 +183,7 @@ struct JNIClassTests { """, """ private static native long $init(); - """ + """, ] ) } @@ -199,7 +213,7 @@ struct JNIClassTests { let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNIValue(in: environment!) } - """ + """, ] ) } @@ -251,7 +265,7 @@ struct JNIClassTests { """, """ private static native void $doSomething(long x, long self); - """ + """, ] ) } @@ -274,7 +288,7 @@ struct JNIClassTests { } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } - """, + """ ] ) } @@ -299,7 +313,7 @@ struct JNIClassTests { """, """ private static native long $copy(long self); - """ + """, ] ) } @@ -325,7 +339,7 @@ struct JNIClassTests { let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNIValue(in: environment!) } - """, + """ ] ) } @@ -350,7 +364,7 @@ struct JNIClassTests { """, """ private static native boolean $isEqual(long other, long self); - """ + """, ] ) } @@ -378,7 +392,7 @@ struct JNIClassTests { } return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) } - """, + """ ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 4696253c..be9cf0ce 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -43,6 +43,10 @@ struct JNIModuleTests { package com.example.swift; + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + import org.swift.swiftkit.core.annotations.*; + public final class SwiftModule { static final String LIB_NAME = "SwiftModule"; @@ -81,6 +85,7 @@ struct JNIModuleTests { * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ + @Unsigned public static char takeIntegers(byte i1, short i2, int i3, long i4) { return SwiftModule.$takeIntegers(i1, i2, i3, i4); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index a4084654..09a8626d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -18,69 +18,81 @@ import Testing @Suite struct JNIStructTests { let source = """ - public struct MyStruct { - let x: Int64 - let y: Int64 - - public init(x: Int64, y: Int64) { - self.x = y - self.y = y + public struct MyStruct { + let x: Int64 + let y: Int64 + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public func doSomething(x: Int64) {} } - - public func doSomething(x: Int64) {} - } - """ + """ @Test func generatesJavaClass() throws { - try assertOutput(input: source, .jni, .java, expectedChunks: [ - """ - // Generated by jextract-swift - // Swift module: SwiftModule + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule - package com.example.swift; - - import org.swift.swiftkit.core.*; - import org.swift.swiftkit.core.util.*; + package com.example.swift; - public final class MyStruct extends JNISwiftInstance { - static final String LIB_NAME = "SwiftModule"; - - @SuppressWarnings("unused") - private static final boolean INITIALIZED_LIBS = initializeLibs(); - static boolean initializeLibs() { - System.loadLibrary(LIB_NAME); - return true; - } - - public MyStruct(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } - """, - """ - private static native void $destroy(long selfPointer); - """, - """ - @Override - protected Runnable $createDestroyFunction() { - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyStruct.$createDestroyFunction", - "this", this, - "self", self$); - } - return new Runnable() { - @Override - public void run() { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); - } - MyStruct.$destroy(self$); + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """,]) + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + public final class MyStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; } - }; - } - """ - ]) + + public MyStruct(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """ + ]) + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + private static native void $destroy(long selfPointer); + """ + ]) + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$createDestroyFunction", + "this", this, + "self", self$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); + } + MyStruct.$destroy(self$); + } + }; + } + """ + ]) } @Test @@ -175,7 +187,7 @@ struct JNIStructTests { """, """ private static native void $doSomething(long x, long self); - """ + """, ] ) } @@ -198,7 +210,7 @@ struct JNIStructTests { } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } - """, + """ ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift new file mode 100644 index 00000000..f4dbffed --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -0,0 +1,154 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +final class JNIUnsignedNumberTests { + + @Test("Import: UInt16 (char)") + func jni_unsignedChar() throws { + try assertOutput( + input: "public func unsignedChar(_ arg: UInt16)", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedChar(_ arg: UInt16) + * } + */ + public static void unsignedChar(@Unsigned char arg) { + SwiftModule.$unsignedChar(arg); + } + """, + """ + private static native void $unsignedChar(char arg); + """, + ] + ) + } + + @Test("Import: UInt32 (annotate)") + func jni_unsignedInt_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + config.logLevel = .trace + + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedInt(_ arg: UInt32) + * } + */ + public static void unsignedInt(@Unsigned int arg) { + SwiftModule.$unsignedInt(arg); + } + private static native void $unsignedInt(int arg); + """, + ] + ) + } + + @Test("Import: return UInt32 (default)") + func jni_returnUnsignedIntDefault() throws { + let config = Configuration() + + try assertOutput( + input: "public func returnUnsignedInt() -> UInt32", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnUnsignedInt() -> UInt32 + * } + */ + @Unsigned + public static int returnUnsignedInt() { + return SwiftModule.$returnUnsignedInt(); + } + private static native int $returnUnsignedInt(); + """, + ] + ) + } + + @Test("Import: return UInt64 (wrap, unsupported)") + func jni_return_unsignedLongWrap() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrapGuava + + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + // we do not import in wrap mode + """ + public final class SwiftModule { + static final String LIB_NAME = "SwiftModule"; + + static { + System.loadLibrary(LIB_NAME); + } + + } + """, + ] + ) + } + + @Test("Import: take UInt64 return UInt32 (annotate)") + func jni_echo_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedLong(first: UInt64, second: UInt32) -> UInt32", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedLong(first: UInt64, second: UInt32) -> UInt32 + * } + */ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return SwiftModule.$unsignedLong(first, second); + } + private static native int $unsignedLong(long first, int second); + """, + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 5757e8da..9d2fcb22 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -20,6 +20,7 @@ struct JNIVariablesTests { let membersSource = """ public class MyClass { + public let someByte: UInt8 public let constant: Int64 public var mutable: Int64 public var computed: Int64 { diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 73357d6b..fd885f1b 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -73,6 +73,7 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -116,6 +117,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -157,6 +159,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -200,6 +203,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -243,6 +247,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -289,6 +294,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -331,6 +337,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -373,6 +380,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -417,6 +425,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift new file mode 100644 index 00000000..11b91e53 --- /dev/null +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -0,0 +1,268 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +final class UnsignedNumberTests { + + @Test("Import: UInt16 (char)") + func unsignedChar() throws { + try assertOutput( + input: "public func unsignedChar(_ arg: UInt16)", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedChar__(uint16_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedChar__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT16 + ); + """, + """ + public static void unsignedChar(@Unsigned char arg) { + swiftjava_SwiftModule_unsignedChar__.call(arg); + } + """, + ] + ) + } + + @Test("Import: UInt32 (wrap)") + func unsignedInt() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrapGuava + + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedInt__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static void unsignedInt(com.google.common.primitives.UnsignedInteger arg) { + swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ) + } + + @Test("Import: UInt32 (annotate)") + func unsignedIntAnnotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedInt__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static void unsignedInt(@Unsigned int arg) { + swiftjava_SwiftModule_unsignedInt__.call(arg); + } + """, + ] + ) + } + + @Test("Import: return UInt32 (default)") + func returnUnsignedIntDefault() throws { + let config = Configuration() + + try assertOutput( + input: "public func returnUnsignedInt() -> UInt32", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_returnUnsignedInt(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedInt { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + @Unsigned + public static int returnUnsignedInt() { + return swiftjava_SwiftModule_returnUnsignedInt.call(); + } + """, + ] + ) + } + + @Test("Import: return UInt64 (wrap)") + func return_unsignedLongWrap() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrapGuava + + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static com.google.common.primitives.UnsignedLong returnUnsignedLong() { + return com.google.common.primitives.UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); + } + """, + ] + ) + } + + @Test("Import: return UInt64 (annotate)") + func return_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + @Unsigned + public static long returnUnsignedLong() { + return swiftjava_SwiftModule_returnUnsignedLong.call(); + } + """, + ] + ) + } + + @Test("Import: take UInt64 (annotate)") + func take_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func takeUnsignedLong(arg: UInt64)", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_takeUnsignedLong_arg(uint64_t arg) + * } + */ + private static class swiftjava_SwiftModule_takeUnsignedLong_arg { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static void takeUnsignedLong(@Unsigned long arg) { + swiftjava_SwiftModule_takeUnsignedLong_arg.call(arg); + } + """, + ] + ) + } + + @Test("Import: take UInt64 return UInt32 (annotate)") + func echo_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedLong(first: UInt64, second: UInt32) -> UInt32", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_unsignedLong_first_second(uint64_t first, uint32_t second) + * } + */ + private static class swiftjava_SwiftModule_unsignedLong_first_second { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + /* first: */SwiftValueLayout.SWIFT_UINT64 + /* second: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); + } + """, + ] + ) + } +} From 2d89c613445a3baf41594529542dc167d4ebc115 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 1 Aug 2025 13:24:49 +0900 Subject: [PATCH 114/178] Fix: JNI + unsigned modes checking (#342) --- .../Commands/JExtractCommand.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 03ca0cd7..105ace45 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -86,10 +86,7 @@ extension SwiftJava.JExtractCommand { config.writeEmptyFiles = writeEmptyFiles config.unsignedNumbersMode = unsignedNumbers - guard checkModeCompatibility() else { - // check would have logged the reason for early exit. - return - } + try checkModeCompatibility() if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -108,18 +105,15 @@ extension SwiftJava.JExtractCommand { } /// Check if the configured modes are compatible, and fail if not - func checkModeCompatibility() -> Bool { + func checkModeCompatibility() throws { if self.mode == .jni { switch self.unsignedNumbers { case .annotate: - print("Error: JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") - return false + throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") case .wrapGuava: () // OK } } - - return true } } @@ -140,5 +134,12 @@ extension SwiftJava.JExtractCommand { } +struct IllegalModeCombinationError: Error { + let message: String + init(_ message: String) { + self.message = message + } +} + extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} From f7a5082b3e2794637b327bc2792a7363042e23a3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 1 Aug 2025 14:05:13 +0900 Subject: [PATCH 115/178] Mark Sendable types as @ThreadSafe (#341) --- .../FFM/FFMSwift2JavaGenerator.swift | 3 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 3 + .../SwiftNominalTypeDeclaration.swift | 16 ++++++ .../swiftkit/core/annotations/ThreadSafe.java | 42 ++++++++++++++ Tests/JExtractSwiftTests/SendableTests.swift | 56 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java create mode 100644 Tests/JExtractSwiftTests/SendableTests.swift diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 29962621..3e60db14 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -278,6 +278,9 @@ extension FFMSwift2JavaGenerator { parentProtocol = "SwiftValue" } + if decl.swiftNominal.isSendable { + printer.print("@ThreadSafe // Sendable") + } printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends FFMSwiftInstance implements \(parentProtocol)") { printer in // Constants diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 63b9dcd1..b91588df 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -171,6 +171,9 @@ extension JNISwift2JavaGenerator { private func printNominal( _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { + if decl.swiftNominal.isSendable { + printer.print("@ThreadSafe // Sendable") + } printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in body(&printer) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index b377fd85..cef4e731 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -85,6 +85,22 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { super.init(moduleName: moduleName, name: node.name.text) } + /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". + lazy var isSendable: Bool = { + // Check if Sendable is in the inheritance list + guard let inheritanceClause = self.syntax?.inheritanceClause else { + return false + } + + for inheritedType in inheritanceClause.inheritedTypes { + if inheritedType.type.trimmedDescription == "Sendable" { + return true + } + } + + return false + }() + /// Determine the known standard library type for this nominal type /// declaration. private func computeKnownStandardLibraryType() -> SwiftKnownTypeDeclKind? { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java new file mode 100644 index 00000000..2e62a8b6 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import jdk.jfr.Description; +import jdk.jfr.Label; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Used to mark a type as thread-safe, i.e. no additional synchronization is necessary when accessing it + * from multiple threads. + * + *

In SwiftJava specifically, this attribute is applied when an extracted Swift type conforms to the Swift + * {@code Sendable} protocol, which is a compiler enforced mechanism to enforce thread-safety in Swift. + * + * @see Swift Sendable API documentation. + */ +@Documented +@Label("Thread-safe") +@Description("Value should be interpreted as safe to be shared across threads.") +@Target({TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ThreadSafe { +} diff --git a/Tests/JExtractSwiftTests/SendableTests.swift b/Tests/JExtractSwiftTests/SendableTests.swift new file mode 100644 index 00000000..d6d0d2d6 --- /dev/null +++ b/Tests/JExtractSwiftTests/SendableTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import Testing + +final class SendableTests { + let source = + """ + public struct SendableStruct: Sendable {} + """ + + + @Test("Import: Sendable struct (ffm)") + func sendableStruct_ffm() throws { + + try assertOutput( + input: source, .ffm, .java, + expectedChunks: [ + """ + @ThreadSafe // Sendable + public final class SendableStruct extends FFMSwiftInstance implements SwiftValue { + static final String LIB_NAME = "SwiftModule"; + static final Arena LIBRARY_ARENA = Arena.ofAuto(); + """, + ] + ) + } + + @Test("Import: Sendable struct (jni)") + func sendableStruct_jni() throws { + + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + @ThreadSafe // Sendable + public final class SendableStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + """, + ] + ) + } + +} \ No newline at end of file From 02de9675d1f227b2814d581b7b56a2c6f0d4f6d9 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 1 Aug 2025 15:12:15 +0900 Subject: [PATCH 116/178] Allow jextract to optionally import package/internal decls as well (#343) --- .../Convenience/SwiftSyntax+Extensions.swift | 69 ++++++++++++++++--- Sources/JExtractSwiftLib/Logger.swift | 17 +++++ .../Swift2JavaTranslator.swift | 4 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 27 +++++--- .../Configuration.swift | 4 ++ .../GenerationMode.swift | 15 +++- .../Commands/JExtractCommand.swift | 5 ++ .../InternalExtractTests.swift | 49 +++++++++++++ 8 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 Tests/JExtractSwiftTests/InternalExtractTests.swift diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index da836d45..e71300af 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -51,23 +51,76 @@ extension DeclModifierSyntax { extension DeclModifierSyntax { var isPublic: Bool { switch self.name.tokenKind { - case .keyword(.private): return false - case .keyword(.fileprivate): return false - case .keyword(.internal): return false - case .keyword(.package): return false - case .keyword(.public): return true - case .keyword(.open): return true - default: return false + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): false + case .keyword(.package): false + case .keyword(.public): true + case .keyword(.open): true + default: false } } + + var isPackage: Bool { + switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): false + case .keyword(.package): true + case .keyword(.public): false + case .keyword(.open): false + default: false + } + } + + var isAtLeastPackage: Bool { + isPackage || isPublic + } + + var isInternal: Bool { + return switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): true + case .keyword(.package): false + case .keyword(.public): false + case .keyword(.open): false + default: false + } + } + + var isAtLeastInternal: Bool { + isInternal || isPackage || isPublic + } } extension WithModifiersSyntax { var isPublic: Bool { - self.modifiers.contains { modifier in + return self.modifiers.contains { modifier in modifier.isPublic } } + + var isAtLeastPackage: Bool { + if self.modifiers.isEmpty { + return false + } + + return self.modifiers.contains { modifier in + modifier.isAtLeastInternal + } + } + + var isAtLeastInternal: Bool { + if self.modifiers.isEmpty { + // we assume that default access level is internal + return true + } + + return self.modifiers.contains { modifier in + modifier.isAtLeastInternal + } + } } extension AttributeListSyntax.Element { diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift index 541dbae4..5bffdc8c 100644 --- a/Sources/JExtractSwiftLib/Logger.swift +++ b/Sources/JExtractSwiftLib/Logger.swift @@ -27,6 +27,23 @@ public struct Logger { self.logLevel = logLevel } + public func error( + _ message: @autoclosure () -> String, + metadata: [String: Any] = [:], + file: String = #fileID, + line: UInt = #line, + function: String = #function + ) { + guard logLevel <= .error else { + return + } + + let metadataString: String = + if metadata.isEmpty { "" } else { "\(metadata)" } + + print("[error][\(file):\(line)](\(function)) \(message()) \(metadataString)") + } + public func warning( _ message: @autoclosure () -> String, metadata: [String: Any] = [:], diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 3e2359f2..300b979f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -200,7 +200,7 @@ extension Swift2JavaTranslator { _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax, parent: ImportedNominalType? ) -> ImportedNominalType? { - if !nominalNode.shouldImport(log: log) { + if !nominalNode.shouldExtract(config: config, log: log) { return nil } @@ -225,7 +225,7 @@ extension Swift2JavaTranslator { guard swiftNominalDecl.moduleName == self.swiftModuleName else { return nil } - guard swiftNominalDecl.syntax!.shouldImport(log: log) else { + guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log) else { return nil } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 90181757..3efddbfc 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -15,9 +15,13 @@ import Foundation import SwiftParser import SwiftSyntax +import JavaKitConfigurationShared final class Swift2JavaVisitor { let translator: Swift2JavaTranslator + var config: Configuration { + self.translator.config + } init(translator: Swift2JavaTranslator) { self.translator = translator @@ -48,7 +52,7 @@ final class Swift2JavaVisitor { case .extensionDecl(let node): self.visit(extensionDecl: node, in: parent) case .typeAliasDecl: - break // TODO: Implement + break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338 case .associatedTypeDecl: break // TODO: Implement @@ -93,7 +97,7 @@ final class Swift2JavaVisitor { } func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldImport(log: log) else { + guard node.shouldExtract(config: config, log: log) else { return } @@ -128,7 +132,7 @@ final class Swift2JavaVisitor { } func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldImport(log: log) else { + guard node.shouldExtract(config: config, log: log) else { return } @@ -182,7 +186,7 @@ final class Swift2JavaVisitor { self.log.info("Initializer must be within a current type; \(node)") return } - guard node.shouldImport(log: log) else { + guard node.shouldExtract(config: config, log: log) else { return } @@ -212,13 +216,20 @@ final class Swift2JavaVisitor { } extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { - func shouldImport(log: Logger) -> Bool { - guard accessControlModifiers.contains(where: { $0.isPublic }) else { - log.trace("Skip import '\(self.qualifiedNameForDebug)': not public") + func shouldExtract(config: Configuration, log: Logger) -> Bool { + let meetsRequiredAccessLevel: Bool = + switch config.effectiveMinimumInputAccessLevelMode { + case .public: self.isPublic + case .package: self.isAtLeastPackage + case .internal: self.isAtLeastInternal + } + + guard meetsRequiredAccessLevel else { + log.debug("Skip import '\(self.qualifiedNameForDebug)': not at least \(config.effectiveMinimumInputAccessLevelMode)") return false } guard !attributes.contains(where: { $0.isJava }) else { - log.trace("Skip import '\(self.qualifiedNameForDebug)': is Java") + log.debug("Skip import '\(self.qualifiedNameForDebug)': is Java") return false } diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index c1ae7dd2..0ff089da 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -46,6 +46,10 @@ public struct Configuration: Codable { public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { unsignedNumbersMode ?? .default } + public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? + public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode { + minimumInputAccessLevelMode ?? .default + } // ==== java 2 swift --------------------------------------------------------- diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index 190323a0..1feac411 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -59,7 +59,20 @@ extension JExtractUnsignedIntegerMode { } } - public static var `default`: JExtractUnsignedIntegerMode { + public static var `default`: Self { .annotate } } + +/// The minimum access level which +public enum JExtractMinimumAccessLevelMode: String, Codable { + case `public` + case `package` + case `internal` +} + +extension JExtractMinimumAccessLevelMode { + public static var `default`: Self { + .public + } +} diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 105ace45..54a99708 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -64,6 +64,9 @@ extension SwiftJava { @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") + var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, @@ -85,6 +88,7 @@ extension SwiftJava.JExtractCommand { config.outputSwiftDirectory = outputSwift config.writeEmptyFiles = writeEmptyFiles config.unsignedNumbersMode = unsignedNumbers + config.minimumInputAccessLevelMode = minimumInputAccessLevel try checkModeCompatibility() @@ -143,3 +147,4 @@ struct IllegalModeCombinationError: Error { extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} +extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} diff --git a/Tests/JExtractSwiftTests/InternalExtractTests.swift b/Tests/JExtractSwiftTests/InternalExtractTests.swift new file mode 100644 index 00000000..78e71236 --- /dev/null +++ b/Tests/JExtractSwiftTests/InternalExtractTests.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +final class InternalExtractTests { + let text = + """ + internal func catchMeIfYouCan() + """ + + @Test("Import: internal decl if configured") + func data_swiftThunk() throws { + var config = Configuration() + config.minimumInputAccessLevelMode = .internal + + try assertOutput( + input: text, + config: config, + .ffm, .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * internal func catchMeIfYouCan() + * } + */ + public static void catchMeIfYouCan() { + swiftjava_SwiftModule_catchMeIfYouCan.call(); + } + """, + ] + ) + } +} \ No newline at end of file From b2f42a170154aa080256ca5a11a857197a52cf81 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 1 Aug 2025 17:34:31 +0200 Subject: [PATCH 117/178] [JExtract/JNI] Support optionals in JNI mode (#340) --- .../Sources/MySwiftLibrary/Optionals.swift | 75 ++++ .../com/example/swift/MySwiftClassTest.java | 4 + .../java/com/example/swift/OptionalsTest.java | 116 +++++ .../Convenience/JavaType+Extensions.swift | 75 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 365 ++++++++++++++- ...wift2JavaGenerator+NativeTranslation.swift | 417 +++++++++++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 21 +- Sources/JExtractSwiftLib/JNI/JNIType.swift | 98 ---- .../Documentation.docc/SupportedFeatures.md | 3 +- .../JNI/JNIClassTests.swift | 12 +- .../JNI/JNIClosureTests.swift | 4 +- .../JNI/JNIJavaKitTests.swift | 10 +- .../JNI/JNIModuleTests.swift | 3 +- .../JNI/JNIOptionalTests.swift | 253 +++++++++++ .../JNI/JNIStructTests.swift | 3 +- .../JNI/JNIVariablesTests.swift | 33 +- 17 files changed, 1300 insertions(+), 212 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java delete mode 100644 Sources/JExtractSwiftLib/JNI/JNIType.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift new file mode 100644 index 00000000..673ecb0b --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaKit + +public func optionalBool(input: Optional) -> Bool? { + return input +} + +public func optionalByte(input: Optional) -> Int8? { + return input +} + +public func optionalChar(input: Optional) -> UInt16? { + return input +} + +public func optionalShort(input: Optional) -> Int16? { + return input +} + +public func optionalInt(input: Optional) -> Int32? { + return input +} + +public func optionalLong(input: Optional) -> Int64? { + return input +} + +public func optionalFloat(input: Optional) -> Float? { + return input +} + +public func optionalDouble(input: Optional) -> Double? { + return input +} + +public func optionalString(input: Optional) -> String? { + return input +} + +public func optionalClass(input: Optional) -> MySwiftClass? { + return input +} + +public func optionalJavaKitLong(input: Optional) -> Int64? { + if let input { + return input.longValue() + } else { + return nil + } +} + +public func multipleOptionals( + input1: Optional, + input2: Optional, + input3: Optional, + input4: Optional, + input5: Optional, + input6: Optional, + input7: Optional +) -> Int64? { + return 1 +} diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index f034b904..e7de03ad 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -17,6 +17,10 @@ import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.OptionalLong; + import static org.junit.jupiter.api.Assertions.*; public class MySwiftClassTest { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java new file mode 100644 index 00000000..f7262ad4 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OptionalsTest { + @Test + void optionalBool() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalBool(Optional.empty())); + assertEquals(Optional.of(true), MySwiftLibrary.optionalBool(Optional.of(true))); + } + + @Test + void optionalByte() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalByte(Optional.empty())); + assertEquals(Optional.of((byte) 1) , MySwiftLibrary.optionalByte(Optional.of((byte) 1))); + } + + @Test + void optionalChar() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalChar(Optional.empty())); + assertEquals(Optional.of((char) 42), MySwiftLibrary.optionalChar(Optional.of((char) 42))); + } + + @Test + void optionalShort() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalShort(Optional.empty())); + assertEquals(Optional.of((short) -250), MySwiftLibrary.optionalShort(Optional.of((short) -250))); + } + + @Test + void optionalInt() { + assertEquals(OptionalInt.empty(), MySwiftLibrary.optionalInt(OptionalInt.empty())); + assertEquals(OptionalInt.of(999), MySwiftLibrary.optionalInt(OptionalInt.of(999))); + } + + @Test + void optionalLong() { + assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalLong(OptionalLong.empty())); + assertEquals(OptionalLong.of(999), MySwiftLibrary.optionalLong(OptionalLong.of(999))); + } + + @Test + void optionalFloat() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalFloat(Optional.empty())); + assertEquals(Optional.of(3.14f), MySwiftLibrary.optionalFloat(Optional.of(3.14f))); + } + + @Test + void optionalDouble() { + assertEquals(OptionalDouble.empty(), MySwiftLibrary.optionalDouble(OptionalDouble.empty())); + assertEquals(OptionalDouble.of(2.718), MySwiftLibrary.optionalDouble(OptionalDouble.of(2.718))); + } + + @Test + void optionalString() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalString(Optional.empty())); + assertEquals(Optional.of("Hello Swift!"), MySwiftLibrary.optionalString(Optional.of("Hello Swift!"))); + } + + @Test + void optionalClass() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(arena); + assertEquals(Optional.empty(), MySwiftLibrary.optionalClass(Optional.empty(), arena)); + Optional optionalClass = MySwiftLibrary.optionalClass(Optional.of(c), arena); + assertTrue(optionalClass.isPresent()); + assertEquals(c.getX(), optionalClass.get().getX()); + } + } + + @Test + void optionalJavaKitLong() { + assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalJavaKitLong(Optional.empty())); + assertEquals(OptionalLong.of(99L), MySwiftLibrary.optionalJavaKitLong(Optional.of(99L))); + } + + @Test + void multipleOptionals() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(arena); + OptionalLong result = MySwiftLibrary.multipleOptionals( + Optional.of((byte) 1), + Optional.of((short) 42), + OptionalInt.of(50), + OptionalLong.of(1000L), + Optional.of("42"), + Optional.of(c), + Optional.of(true) + ); + assertEquals(result, OptionalLong.of(1L)); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index f9a67419..cb849e79 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -36,4 +36,79 @@ extension JavaType { case .void: fatalError("There is no type signature for 'void'") } } + + /// Returns the next integral type with space for self and an additional byte. + var nextIntergralTypeWithSpaceForByte: (javaType: JavaType, swiftType: SwiftKnownTypeDeclKind, valueBytes: Int)? { + switch self { + case .boolean, .byte: (.short, .int16, 1) + case .char, .short: (.int, .int32, 2) + case .int: (.long, .int64, 4) + default: nil + } + } + + var optionalType: String? { + switch self { + case .boolean: "Optional" + case .byte: "Optional" + case .char: "Optional" + case .short: "Optional" + case .int: "OptionalInt" + case .long: "OptionalLong" + case .float: "Optional" + case .double: "OptionalDouble" + case .javaLangString: "Optional" + default: nil + } + } + + var optionalWrapperType: String? { + switch self { + case .boolean, .byte, .char, .short, .float, .javaLangString: "Optional" + case .int: "OptionalInt" + case .long: "OptionalLong" + case .double: "OptionalDouble" + default: nil + } + } + + var optionalPlaceholderValue: String? { + switch self { + case .boolean: "false" + case .byte: "(byte) 0" + case .char: "(char) 0" + case .short: "(short) 0" + case .int: "0" + case .long: "0L" + case .float: "0f" + case .double: "0.0" + case .array, .class: "null" + case .void: nil + } + } + + var jniCallMethodAName: String { + switch self { + case .boolean: "CallBooleanMethodA" + case .byte: "CallByteMethodA" + case .char: "CallCharMethodA" + case .short: "CallShortMethodA" + case .int: "CallIntMethodA" + case .long: "CallLongMethodA" + case .float: "CallFloatMethodA" + case .double: "CallDoubleMethodA" + case .void: "CallVoidMethodA" + default: "CallObjectMethodA" + } + } + + /// Returns whether this type returns `JavaValue` from JavaKit + var implementsJavaValue: Bool { + return switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: + true + default: + false + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index b91588df..0daf14de 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import JavaTypes // MARK: Defaults @@ -20,6 +21,7 @@ extension JNISwift2JavaGenerator { static let defaultJavaImports: Array = [ "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", + "java.util.*", // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", @@ -279,11 +281,15 @@ extension JNISwift2JavaGenerator { let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl let nativeSignature = translatedDecl.nativeFunctionSignature let resultType = nativeSignature.result.javaType - var parameters = nativeSignature.parameters - if let selfParameter = nativeSignature.selfParameter { - parameters.append(selfParameter) + var parameters = nativeSignature.parameters.flatMap(\.parameters) + if let selfParameter = nativeSignature.selfParameter?.parameters { + parameters += selfParameter } - let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ") + parameters += nativeSignature.result.outParameters + + let renderedParameters = parameters.map { javaParameter in + "\(javaParameter.type) \(javaParameter.name)" + }.joined(separator: ", ") printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));") } @@ -308,6 +314,12 @@ extension JNISwift2JavaGenerator { arguments.append(lowered) } + // Indirect return receivers + for outParameter in translatedFunctionSignature.resultType.outParameters { + printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());") + arguments.append(outParameter.name) + } + //=== Part 3: Downcall. // TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names // using the registry? diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 5908cfb1..0db77ece 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -190,14 +190,26 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateOptionalParameter( + wrappedType: genericArgs[0], + parameterName: parameterName + ) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), + conversion: .placeholder + ) } - - return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), - conversion: .placeholder - ) } if nominalType.isJavaKitWrapper { @@ -237,13 +249,22 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalParameter( + wrappedType: wrapped, + parameterName: parameterName + ) + + case .metatype, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } - func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, - mode: JExtractUnsignedIntegerMode) -> JavaNativeConversionStep { + func unsignedResultConversion( + _ from: SwiftType, + to javaType: JavaType, + mode: JExtractUnsignedIntegerMode + ) -> JavaNativeConversionStep { switch mode { case .annotate: return .placeholder // no conversions @@ -253,6 +274,75 @@ extension JNISwift2JavaGenerator { } } + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + parameterName: String + ) throws -> TranslatedParameter { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + guard let translatedClass = javaType.optionalType, let placeholderValue = javaType.optionalPlaceholderValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: JavaType(className: translatedClass), + annotations: parameterAnnotations + ), + conversion: .commaSeparated([ + .isOptionalPresent, + .method(.placeholder, function: "orElse", arguments: [.constant(placeholderValue)]) + ]) + ) + } + + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: "Optional<\(javaType)>"), + annotations: parameterAnnotations + ), + conversion: .method( + .placeholder, + function: "orElse", + arguments: [.constant("null")] + ) + ) + } + + // Assume JExtract imported class + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: "Optional<\(nominalTypeName)>"), + annotations: parameterAnnotations + ), + conversion: .method( + .method(.placeholder, function: "map", arguments: [.constant("\(nominalType)::$memoryAddress")]), + function: "orElse", + arguments: [.constant("0L")] + ) + ) + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + func translate(swiftResult: SwiftResult) throws -> TranslatedResult { let swiftType = swiftResult.type @@ -262,15 +352,25 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateOptionalResult(wrappedType: genericArgs[0]) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedResult( + javaType: javaType, + annotations: resultAnnotations, + outParameters: [], + conversion: .placeholder + ) } - - return TranslatedResult( - javaType: javaType, - annotations: resultAnnotations, - conversion: .placeholder - ) } if nominalType.isJavaKitWrapper { @@ -282,13 +382,94 @@ extension JNISwift2JavaGenerator { return TranslatedResult( javaType: javaType, annotations: resultAnnotations, + outParameters: [], conversion: .constructSwiftValue(.placeholder, javaType) ) case .tuple([]): - return TranslatedResult(javaType: .void, conversion: .placeholder) + return TranslatedResult(javaType: .void, outParameters: [], conversion: .placeholder) + + case .optional(let wrapped): + return try translateOptionalResult(wrappedType: wrapped) - case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + + func translateOptionalResult( + wrappedType swiftType: SwiftType + ) throws -> TranslatedResult { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + guard let returnType = javaType.optionalType, let optionalClass = javaType.optionalWrapperType else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Check if we can fit the value and a discriminator byte in a primitive. + // so the return JNI value will be (value, discriminator) + if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte { + return TranslatedResult( + javaType: .class(package: nil, name: returnType), + annotations: parameterAnnotations, + outParameters: [], + conversion: .combinedValueToOptional( + .placeholder, + nextIntergralTypeWithSpaceForByte.javaType, + valueType: javaType, + valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes, + optionalType: optionalClass + ) + ) + } else { + // Otherwise, we return the result as normal, but + // use an indirect return for the discriminator. + return TranslatedResult( + javaType: .class(package: nil, name: returnType), + annotations: parameterAnnotations, + outParameters: [ + OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + ], + conversion: .toOptionalFromIndirectReturn( + discriminatorName: "result_discriminator$", + optionalClass: optionalClass, + javaType: javaType, + toValue: .placeholder + ) + ) + } + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // We assume this is a JExtract class. + let returnType = JavaType.class(package: nil, name: "Optional<\(nominalTypeName)>") + return TranslatedResult( + javaType: returnType, + annotations: parameterAnnotations, + outParameters: [ + OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + ], + conversion: .toOptionalFromIndirectReturn( + discriminatorName: "result_discriminator$", + optionalClass: "Optional", + javaType: .long, + toValue: .constructSwiftValue(.placeholder, .class(package: nil, name: nominalTypeName)) + ) + ) + + default: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -348,10 +529,33 @@ extension JNISwift2JavaGenerator { /// Java annotations that should be propagated from the result type onto the method var annotations: [JavaAnnotation] = [] + let outParameters: [OutParameter] + /// Represents how to convert the Java native result into a user-facing result. let conversion: JavaNativeConversionStep } + struct OutParameter { + enum Allocation { + case newArray(JavaType, size: Int) + + func render() -> String { + switch self { + case .newArray(let javaType, let size): + "new \(javaType)[\(size)]" + } + } + } + + let name: String + let type: JavaType + let allocation: Allocation + + var javaParameter: JavaParameter { + JavaParameter(name: self.name, type: self.type) + } + } + /// Represent a Swift closure type in the user facing Java API. /// /// Closures are translated to named functional interfaces in Java. @@ -367,6 +571,11 @@ extension JNISwift2JavaGenerator { /// The value being converted case placeholder + case constant(String) + + // Convert the results of the inner steps to a comma separated list. + indirect case commaSeparated([JavaNativeConversionStep]) + /// `value.$memoryAddress()` indirect case valueMemoryAddress(JavaNativeConversionStep) @@ -375,6 +584,43 @@ extension JNISwift2JavaGenerator { indirect case call(JavaNativeConversionStep, function: String) + indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) + + case isOptionalPresent + + indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, valueType: JavaType, valueSizeInBytes: Int, optionalType: String) + + indirect case ternary(JavaNativeConversionStep, thenExp: JavaNativeConversionStep, elseExp: JavaNativeConversionStep) + + indirect case equals(JavaNativeConversionStep, JavaNativeConversionStep) + + indirect case subscriptOf(JavaNativeConversionStep, arguments: [JavaNativeConversionStep]) + + static func toOptionalFromIndirectReturn( + discriminatorName: String, + optionalClass: String, + javaType: JavaType, + toValue valueConversion: JavaNativeConversionStep + ) -> JavaNativeConversionStep { + .aggregate( + name: "result$", + type: javaType, + [ + .ternary( + .equals( + .subscriptOf(.constant(discriminatorName), arguments: [.constant("0")]), + .constant("1") + ), + thenExp: .method(.constant(optionalClass), function: "of", arguments: [valueConversion]), + elseExp: .method(.constant(optionalClass), function: "empty") + ) + ] + ) + } + + /// Perform multiple conversions using the same input. + case aggregate(name: String, type: JavaType, [JavaNativeConversionStep]) + /// 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. @@ -383,6 +629,12 @@ extension JNISwift2JavaGenerator { case .placeholder: return placeholder + case .constant(let value): + return value + + case .commaSeparated(let list): + return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ") + case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" @@ -394,13 +646,63 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(function)(\(inner))" + + case .isOptionalPresent: + return "(byte) (\(placeholder).isPresent() ? 1 : 0)" + + case .method(let inner, let methodName, let arguments): + let inner = inner.render(&printer, placeholder) + let args = arguments.map { $0.render(&printer, placeholder) } + let argsStr = args.joined(separator: ", ") + return "\(inner).\(methodName)(\(argsStr))" + + case .combinedValueToOptional(let combined, let combinedType, let valueType, let valueSizeInBytes, let optionalType): + let combined = combined.render(&printer, placeholder) + printer.print( + """ + \(combinedType) combined$ = \(combined); + byte discriminator$ = (byte) (combined$ & 0xFF); + """ + ) + + if valueType == .boolean { + printer.print("boolean value$ = ((byte) (combined$ >> 8)) != 0;") + } else { + printer.print("\(valueType) value$ = (\(valueType)) (combined$ >> \(valueSizeInBytes * 8));") + } + + return "discriminator$ == 1 ? \(optionalType).of(value$) : \(optionalType).empty()" + + case .ternary(let cond, let thenExp, let elseExp): + let cond = cond.render(&printer, placeholder) + let thenExp = thenExp.render(&printer, placeholder) + let elseExp = elseExp.render(&printer, placeholder) + return "(\(cond)) ? \(thenExp) : \(elseExp)" + + case .equals(let lhs, let rhs): + let lhs = lhs.render(&printer, placeholder) + let rhs = rhs.render(&printer, placeholder) + return "\(lhs) == \(rhs)" + + case .subscriptOf(let inner, let arguments): + let inner = inner.render(&printer, placeholder) + let arguments = arguments.map { $0.render(&printer, placeholder) } + return "\(inner)[\(arguments.joined(separator: ", "))]" + + case .aggregate(let name, let type, let steps): + precondition(!steps.isEmpty, "Aggregate must contain steps") + printer.print("\(type) \(name) = \(placeholder);") + let steps = steps.map { + $0.render(&printer, name) + } + return steps.last! } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder: + case .placeholder, .constant, .isOptionalPresent: return false case .constructSwiftValue: @@ -409,6 +711,27 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + case .commaSeparated(let list): + return list.contains(where: { $0.requiresSwiftArena }) + + case .method(let inner, _, let args): + return inner.requiresSwiftArena || args.contains(where: \.requiresSwiftArena) + + case .combinedValueToOptional(let inner, _, _, _, _): + return inner.requiresSwiftArena + + case .ternary(let cond, let thenExp, let elseExp): + return cond.requiresSwiftArena || thenExp.requiresSwiftArena || elseExp.requiresSwiftArena + + case .equals(let lhs, let rhs): + return lhs.requiresSwiftArena || rhs.requiresSwiftArena + + case .subscriptOf(let inner, _): + return inner.requiresSwiftArena + + case .aggregate(_, _, let steps): + return steps.contains(where: \.requiresSwiftArena) + case .call(let inner, _): return inner.requiresSwiftArena } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 9f1113fc..e7f7efbe 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -72,16 +72,30 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), - javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) - } + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + return try translateOptionalParameter( + wrappedType: genericArgs[0], + parameterName: parameterName + ) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) + ) - return NativeParameter( - name: parameterName, - javaType: javaType, - conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) - ) + } } if nominalType.isJavaKitWrapper { @@ -90,23 +104,33 @@ extension JNISwift2JavaGenerator { } return NativeParameter( - name: parameterName, - javaType: javaType, - conversion: .initializeJavaKitWrapper(wrapperName: nominalTypeName) + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .initializeJavaKitWrapper( + .unwrapOptional( + .placeholder, + name: parameterName, + fatalErrorMessage: "\(parameterName) was null in call to \\(#function), but Swift requires non-optional!" + ), + wrapperName: nominalTypeName + ) ) } // JExtract classes are passed as the pointer. return NativeParameter( - name: parameterName, - javaType: .long, + parameters: [ + JavaParameter(name: parameterName, type: .long) + ], conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) ) case .tuple([]): return NativeParameter( - name: parameterName, - javaType: .void, + parameters: [ + JavaParameter(name: parameterName, type: .void) + ], conversion: .placeholder ) @@ -124,19 +148,162 @@ extension JNISwift2JavaGenerator { let result = try translateClosureResult(fn.resultType) return NativeParameter( - name: parameterName, - javaType: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + parameters: [ + JavaParameter(name: parameterName, type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)")) + ], conversion: .closureLowering( parameters: parameters, result: result ) ) - case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalParameter( + wrappedType: wrapped, + parameterName: parameterName + ) + + case .metatype, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } } + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + let discriminatorName = "\(parameterName)_discriminator" + let valueName = "\(parameterName)_value" + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: discriminatorName, type: .byte), + JavaParameter(name: valueName, type: javaType) + ], + conversion: .optionalLowering( + .initFromJNI(.placeholder, swiftType: swiftType), + discriminatorName: discriminatorName, + valueName: valueName + ) + ) + } + + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .optionalMap(.initializeJavaKitWrapper(.placeholder, wrapperName: nominalTypeName)) + ) + } + + // Assume JExtract wrapped class + return NativeParameter( + parameters: [JavaParameter(name: parameterName, type: .long)], + conversion: .pointee( + .optionalChain( + .extractSwiftValue( + .placeholder, + swiftType: swiftType, + allowNil: true + ) + ) + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + + func translateOptionalResult( + wrappedType swiftType: SwiftType + ) throws -> NativeResult { + switch swiftType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Check if we can fit the value and a discriminator byte in a primitive. + // so the return JNI value will be (value, discriminator) + if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte { + return NativeResult( + javaType: nextIntergralTypeWithSpaceForByte.javaType, + conversion: .getJNIValue( + .optionalRaisingWidenIntegerType( + .placeholder, + valueType: javaType, + combinedSwiftType: nextIntergralTypeWithSpaceForByte.swiftType, + valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes + ) + ), + outParameters: [] + ) + } else { + // Use indirect byte array to store discriminator + let discriminatorName = "result_discriminator$" + + return NativeResult( + javaType: javaType, + conversion: .optionalRaisingIndirectReturn( + .getJNIValue(.placeholder), + returnType: javaType, + discriminatorParameterName: discriminatorName, + placeholderValue: .member( + .constant("\(swiftType)"), + member: "jniPlaceholderValue" + ) + ), + outParameters: [ + JavaParameter(name: discriminatorName, type: .array(.byte)) + ] + ) + } + } + + guard !nominalType.isJavaKitWrapper else { + // TODO: Should be the same as above + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Assume JExtract imported class + let discriminatorName = "result_discriminator$" + + return NativeResult( + javaType: .long, + conversion: .optionalRaisingIndirectReturn( + .getJNIValue(.allocateSwiftValue(name: "_result", swiftType: swiftType)), + returnType: .long, + discriminatorParameterName: discriminatorName, + placeholderValue: .constant("0") + ), + outParameters: [ + JavaParameter(name: discriminatorName, type: .array(.byte)) + ] + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + func translateClosureResult( _ type: SwiftType ) throws -> NativeResult { @@ -151,7 +318,8 @@ extension JNISwift2JavaGenerator { // Only support primitives for now. return NativeResult( javaType: javaType, - conversion: .initFromJNI(.placeholder, swiftType: type) + conversion: .initFromJNI(.placeholder, swiftType: type), + outParameters: [] ) } @@ -161,7 +329,8 @@ extension JNISwift2JavaGenerator { case .tuple([]): return NativeResult( javaType: .void, - conversion: .placeholder + conversion: .placeholder, + outParameters: [] ) case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: @@ -183,8 +352,9 @@ extension JNISwift2JavaGenerator { // Only support primitives for now. return NativeParameter( - name: parameterName, - javaType: javaType, + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], conversion: .getJValue(.placeholder) ) } @@ -203,17 +373,24 @@ extension JNISwift2JavaGenerator { switch swiftResult.type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + return try translateOptionalResult(wrappedType: genericArgs[0]) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return NativeResult( + javaType: javaType, + conversion: .getJNIValue(.placeholder), + outParameters: [] + ) } - guard javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) - } - - return NativeResult( - javaType: javaType, - conversion: .getJNIValue(.placeholder) - ) } if nominalType.isJavaKitWrapper { @@ -222,20 +399,23 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) + conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)), + outParameters: [] ) case .tuple([]): return NativeResult( javaType: .void, - conversion: .placeholder + conversion: .placeholder, + outParameters: [] ) - case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalResult(wrappedType: wrapped) + + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - - } } @@ -246,12 +426,9 @@ extension JNISwift2JavaGenerator { } struct NativeParameter { - let name: String - let javaType: JavaType - - var jniType: JNIType { - javaType.jniType - } + /// One Swift parameter can be lowered to multiple parameters. + /// E.g. 'Optional' as (descriptor, value) pair. + var parameters: [JavaParameter] /// Represents how to convert the JNI parameter to a Swift parameter let conversion: NativeSwiftConversionStep @@ -260,6 +437,9 @@ extension JNISwift2JavaGenerator { struct NativeResult { let javaType: JavaType let conversion: NativeSwiftConversionStep + + /// Out parameters for populating the indirect return values. + var outParameters: [JavaParameter] } /// Describes how to convert values between Java types and Swift through JNI @@ -267,6 +447,8 @@ extension JNISwift2JavaGenerator { /// The value being converted case placeholder + case constant(String) + /// `value.getJNIValue(in:)` indirect case getJNIValue(NativeSwiftConversionStep) @@ -277,7 +459,11 @@ extension JNISwift2JavaGenerator { indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) /// Extracts a swift type at a pointer given by a long. - indirect case extractSwiftValue(NativeSwiftConversionStep, swiftType: SwiftType) + indirect case extractSwiftValue( + NativeSwiftConversionStep, + swiftType: SwiftType, + allowNil: Bool = false + ) /// Allocate memory for a Swift value and outputs the pointer case allocateSwiftValue(name: String, swiftType: SwiftType) @@ -288,7 +474,23 @@ extension JNISwift2JavaGenerator { indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) - case initializeJavaKitWrapper(wrapperName: String) + indirect case initializeJavaKitWrapper(NativeSwiftConversionStep, wrapperName: String) + + indirect case optionalLowering(NativeSwiftConversionStep, discriminatorName: String, valueName: String) + + indirect case optionalChain(NativeSwiftConversionStep) + + indirect case optionalRaisingWidenIntegerType(NativeSwiftConversionStep, valueType: JavaType, combinedSwiftType: SwiftKnownTypeDeclKind, valueSizeInBytes: Int) + + indirect case optionalRaisingIndirectReturn(NativeSwiftConversionStep, returnType: JavaType, discriminatorParameterName: String, placeholderValue: NativeSwiftConversionStep) + + indirect case method(NativeSwiftConversionStep, function: String, arguments: [(String?, NativeSwiftConversionStep)] = []) + + indirect case member(NativeSwiftConversionStep, member: String) + + indirect case optionalMap(NativeSwiftConversionStep) + + indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -298,6 +500,9 @@ extension JNISwift2JavaGenerator { case .placeholder: return placeholder + case .constant(let value): + return value + case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) return "\(inner).getJNIValue(in: environment!)" @@ -310,18 +515,28 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment!)" - case .extractSwiftValue(let inner, let swiftType): + case .extractSwiftValue(let inner, let swiftType, let allowNil): let inner = inner.render(&printer, placeholder) + let pointerName = "\(inner)$" + if !allowNil { + printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#) + } printer.print( """ - assert(\(inner) != 0, "\(inner) memory address was null") let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) - guard let \(inner)$ = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) else { - fatalError("\(inner) memory address was null in call to \\(#function)!") - } + let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) """ ) - return "\(inner)$" + if !allowNil { + printer.print( + """ + guard let \(pointerName) else { + fatalError("\(inner) memory address was null in call to \\(#function)!") + } + """ + ) + } + return pointerName case .allocateSwiftValue(let name, let swiftType): let pointerName = "\(name)$" @@ -344,15 +559,17 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature( resultType: nativeResult.javaType, - parameterTypes: parameters.map(\.javaType) + parameterTypes: parameters.flatMap { $0.parameters.map(\.type) } ) - let closureParameters = !parameters.isEmpty ? "\(parameters.map(\.name).joined(separator: ", ")) in" : "" + let names = parameters.flatMap { $0.parameters.map(\.name) } + let closureParameters = !parameters.isEmpty ? "\(names.joined(separator: ", ")) in" : "" printer.print("{ \(closureParameters)") printer.indent() + // TODO: Add support for types that are lowered to multiple parameters in closures let arguments = parameters.map { - $0.conversion.render(&printer, $0.name) + $0.conversion.render(&printer, $0.parameters.first!.name) } printer.print( @@ -363,7 +580,7 @@ extension JNISwift2JavaGenerator { """ ) - let upcall = "environment!.interface.\(nativeResult.javaType.jniType.callMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let upcall = "environment!.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" let result = nativeResult.conversion.render(&printer, upcall) if nativeResult.javaType.isVoid { @@ -377,8 +594,92 @@ extension JNISwift2JavaGenerator { return printer.finalize() - case .initializeJavaKitWrapper(let wrapperName): - return "\(wrapperName)(javaThis: \(placeholder), environment: environment!)" + case .initializeJavaKitWrapper(let inner, let wrapperName): + let inner = inner.render(&printer, placeholder) + return "\(wrapperName)(javaThis: \(inner), environment: environment!)" + + case .optionalLowering(let valueConversion, let discriminatorName, let valueName): + let value = valueConversion.render(&printer, valueName) + return "\(discriminatorName) == 1 ? \(value) : nil" + + case .optionalChain(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner)?" + + case .optionalRaisingWidenIntegerType(let inner, let valueType, let combinedSwiftType, let valueSizeInBytes): + let inner = inner.render(&printer, placeholder) + let value = valueType == .boolean ? "$0 ? 1 : 0" : "$0" + let combinedSwiftTypeName = combinedSwiftType.moduleAndName.name + printer.print( + """ + let value$ = \(inner).map { + \(combinedSwiftTypeName)(\(value)) << \(valueSizeInBytes * 8) | \(combinedSwiftTypeName)(1) + } ?? 0 + """ + ) + return "value$" + + case .optionalRaisingIndirectReturn(let inner, let returnType, let discriminatorParameterName, let placeholderValue): + printer.print("let result$: \(returnType.jniTypeName)") + printer.printBraceBlock("if let innerResult$ = \(placeholder)") { printer in + let inner = inner.render(&printer, "innerResult$") + printer.print( + """ + result$ = \(inner) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, \(discriminatorParameterName), 0, 1, &flag$) + """ + ) + } + printer.printBraceBlock("else") { printer in + let placeholderValue = placeholderValue.render(&printer, placeholder) + printer.print( + """ + result$ = \(placeholderValue) + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, \(discriminatorParameterName), 0, 1, &flag$) + """ + ) + } + + return "result$" + + case .method(let inner, let methodName, let arguments): + let inner = inner.render(&printer, placeholder) + let args = arguments.map { name, value in + let value = value.render(&printer, placeholder) + if let name { + return "\(name): \(value)" + } else { + return value + } + } + let argsStr = args.joined(separator: ", ") + return "\(inner).\(methodName)(\(argsStr))" + + case .member(let inner, let member): + let inner = inner.render(&printer, placeholder) + return "\(inner).\(member)" + + case .optionalMap(let inner): + var printer = CodePrinter() + printer.printBraceBlock("\(placeholder).map") { printer in + let inner = inner.render(&printer, "$0") + printer.print("return \(inner)") + } + return printer.finalize() + + case .unwrapOptional(let inner, let name, let fatalErrorMessage): + let unwrappedName = "\(name)_unwrapped$" + let inner = inner.render(&printer, placeholder) + printer.print( + """ + guard let \(unwrappedName) = \(inner) else { + fatalError("\(fatalErrorMessage)") + } + """ + ) + return unwrappedName } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 8b910ffb..ca580e60 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,18 +125,20 @@ extension JNISwift2JavaGenerator { } let nativeSignature = translatedDecl.nativeFunctionSignature - var parameters = nativeSignature.parameters + var parameters = nativeSignature.parameters.flatMap(\.parameters) if let selfParameter = nativeSignature.selfParameter { - parameters.append(selfParameter) + parameters += selfParameter.parameters } + parameters += nativeSignature.result.outParameters + printCDecl( &printer, javaMethodName: translatedDecl.nativeFunctionName, parentName: translatedDecl.parentName, - parameters: parameters.map { JavaParameter(name: $0.name, type: $0.javaType) }, - resultType: nativeSignature.result.javaType.jniType + parameters: parameters, + resultType: nativeSignature.result.javaType ) { printer in self.printFunctionDowncall(&printer, decl) } @@ -155,8 +157,9 @@ extension JNISwift2JavaGenerator { // Regular parameters. var arguments = [String]() - for parameter in nativeSignature.parameters { - let lowered = parameter.conversion.render(&printer, parameter.name) + for (idx, parameter) in nativeSignature.parameters.enumerated() { + let javaParameterName = translatedDecl.translatedFunctionSignature.parameters[idx].parameter.name + let lowered = parameter.conversion.render(&printer, javaParameterName) arguments.append(lowered) } @@ -230,7 +233,7 @@ extension JNISwift2JavaGenerator { javaMethodName: String, parentName: String, parameters: [JavaParameter], - resultType: JNIType, + resultType: JavaType, _ body: (inout CodePrinter) -> Void ) { let jniSignature = parameters.reduce(into: "") { signature, parameter in @@ -246,7 +249,7 @@ extension JNISwift2JavaGenerator { + jniSignature.escapedJNIIdentifier let translatedParameters = parameters.map { - "\($0.name): \($0.type.jniType)" + "\($0.name): \($0.type.jniTypeName)" } let thunkParameters = @@ -254,7 +257,7 @@ extension JNISwift2JavaGenerator { "environment: UnsafeMutablePointer!", "thisClass: jclass" ] + translatedParameters - let thunkReturnType = resultType != .void ? " -> \(resultType)" : "" + let thunkReturnType = resultType != .void ? " -> \(resultType.jniTypeName)" : "" // TODO: Think about function overloads printer.printBraceBlock( diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift deleted file mode 100644 index cdedb0a1..00000000 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ /dev/null @@ -1,98 +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 -// -//===----------------------------------------------------------------------===// - -import JavaTypes - -/// Represents types that are able to be passed over a JNI boundary. -/// -/// - SeeAlso: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html -enum JNIType { - case jboolean - case jfloat - case jdouble - case jbyte - case jchar - case jshort - case jint - case jlong - case void - case jstring - case jclass - case jthrowable - case jobject - case jbooleanArray - case jbyteArray - case jcharArray - case jshortArray - case jintArray - case jlongArray - case jfloatArray - case jdoubleArray - case jobjectArray - - var callMethodAName: String { - switch self { - case .jboolean: "CallBooleanMethodA" - case .jbyte: "CallByteMethodA" - case .jchar: "CallCharMethodA" - case .jshort: "CallShortMethodA" - case .jint: "CallIntMethodA" - case .jlong: "CallLongMethodA" - case .jfloat: "CallFloatMethodA" - case .jdouble: "CallDoubleMethodA" - case .void: "CallVoidMethodA" - case .jobject, .jstring, .jclass, .jthrowable: "CallObjectMethodA" - case .jbooleanArray, .jbyteArray, .jcharArray, .jshortArray, .jintArray, .jlongArray, .jfloatArray, .jdoubleArray, .jobjectArray: "CallObjectMethodA" - } - } -} - -extension JavaType { - var jniType: JNIType { - switch self { - case .boolean: .jboolean - case .byte: .jbyte - case .char: .jchar - case .short: .jshort - case .int: .jint - case .long: .jlong - case .float: .jfloat - case .double: .jdouble - case .void: .void - case .array(.boolean): .jbooleanArray - case .array(.byte): .jbyteArray - case .array(.char): .jcharArray - case .array(.short): .jshortArray - case .array(.int): .jintArray - case .array(.long): .jlongArray - case .array(.float): .jfloatArray - case .array(.double): .jdoubleArray - case .array: .jobjectArray - case .javaLangString: .jstring - case .javaLangClass: .jclass - case .javaLangThrowable: .jthrowable - case .class: .jobject - } - } - - /// Returns whether this type returns `JavaValue` from JavaKit - var implementsJavaValue: Bool { - return switch self { - case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: - true - default: - false - } - } -} diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index b3d02276..130c333b 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -63,7 +63,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | | Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | | Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | -| Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | +| Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | +| Optional return types: `func f() -> Int?`, `func g() -> MyClass?` | ❌ | ✅ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | | Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | | Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 483d53f5..5b015f89 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -283,7 +283,8 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) @@ -331,7 +332,8 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } let result$ = UnsafeMutablePointer.allocate(capacity: 1) @@ -382,12 +384,14 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { assert(other != 0, "other memory address was null") let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) - guard let other$ = UnsafeMutablePointer(bitPattern: otherBits$) else { + let other$ = UnsafeMutablePointer(bitPattern: otherBits$) + guard let other$ else { fatalError("other memory address was null in call to \\(#function)!") } assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 47d7e35d..9a388da1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -61,7 +61,7 @@ struct JNIClosureTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") - func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { let class$ = environment!.interface.GetObjectClass(environment, closure) let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! @@ -113,7 +113,7 @@ struct JNIClosureTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") - func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in let class$ = environment!.interface.GetObjectClass(environment, closure) let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index 0283780a..1f19c8f9 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -64,8 +64,14 @@ struct JNIJavaKitTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J") - func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject, javaInteger: jobject, int: jlong) { - SwiftModule.function(javaLong: JavaLong(javaThis: javaLong, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject?, javaInteger: jobject?, int: jlong) { + guard let javaLong_unwrapped$ = javaLong else { + fatalError("javaLong was null in call to \\(#function), but Swift requires non-optional!") + } + guard let javaInteger_unwrapped$ = javaInteger else { + fatalError("javaInteger was null in call to \\(#function), but Swift requires non-optional!") + } + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment!), int: Int64(fromJNI: int, in: environment!)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index be9cf0ce..198276ba 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -45,6 +45,7 @@ struct JNIModuleTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; + import java.util.*; import org.swift.swiftkit.core.annotations.*; public final class SwiftModule { @@ -176,7 +177,7 @@ struct JNIModuleTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") - func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring) -> jstring { + func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift new file mode 100644 index 00000000..cd04660b --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.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 JExtractSwiftLib +import Testing + +@Suite +struct JNIOptionalTests { + let source = + """ + class MyClass { } + + public func optionalSugar(_ arg: Int64?) -> Int32? + public func optionalExplicit(_ arg: Optional) -> Optional + public func optionalClass(_ arg: MyClass?) -> MyClass? + public func optionalJavaKitClass(_ arg: JavaLong?) + """ + + let classLookupTable = [ + "JavaLong": "java.lang.Long", + ] + + @Test + func optionalSugar_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalSugar(_ arg: Int64?) -> Int32? + * } + */ + public static OptionalInt optionalSugar(OptionalLong arg) { + long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); + byte discriminator$ = (byte) (combined$ & 0xFF); + int value$ = (int) (combined$ >> 32); + return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); + } + """, + """ + private static native long $optionalSugar(byte arg_discriminator, long arg_value); + """ + ] + ) + } + + @Test + func optionalSugar_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") + func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { + let value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + Int64($0) << 32 | Int64(1) + } ?? 0 + return value$.getJNIValue(in: environment!) + } + """ + ] + ) + } + + @Test + func optionalExplicit_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalExplicit(_ arg: Optional) -> Optional + * } + */ + public static Optional optionalExplicit(Optional arg) { + byte[] result_discriminator$ = new byte[1]; + java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); + } + """, + """ + private static native java.lang.String $optionalExplicit(byte arg_discriminator, java.lang.String arg_value, byte[] result_discriminator$); + """ + ] + ) + } + + @Test + func optionalExplicit_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B") + func Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jstring?, result_discriminator$: jbyteArray?) -> jstring? { + let result$: jstring? + if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment!) : nil) { + result$ = innerResult$.getJNIValue(in: environment!) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + else { + result$ = String.jniPlaceholderValue + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + return result$ + } + """ + ] + ) + } + + @Test + func optionalClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalClass(_ arg: MyClass?) -> MyClass? + * } + */ + public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { + byte[] result_discriminator$ = new byte[1]; + long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(new MyClass(result$, swiftArena$)) : Optional.empty(); + } + """, + """ + private static native long $optionalClass(long arg, byte[] result_discriminator$); + """ + ] + ) + } + + @Test + func optionalClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalClass__J_3B") + func Java_com_example_swift_SwiftModule__00024optionalClass__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, result_discriminator$: jbyteArray?) -> jlong { + let argBits$ = Int(Int64(fromJNI: arg, in: environment!)) + let arg$ = UnsafeMutablePointer(bitPattern: argBits$) + let result$: jlong + if let innerResult$ = SwiftModule.optionalClass(arg$?.pointee) { + let _result$ = UnsafeMutablePointer.allocate(capacity: 1) + _result$.initialize(to: innerResult$) + let _resultBits$ = Int64(Int(bitPattern: _result$)) + result$ = _resultBits$.getJNIValue(in: environment!) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + else { + result$ = 0 + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + return result$ + } + """ + ] + ) + } + + @Test + func optionalJavaKitClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalJavaKitClass(_ arg: JavaLong?) + * } + */ + public static void optionalJavaKitClass(Optional arg) { + SwiftModule.$optionalJavaKitClass(arg.orElse(null)); + } + """, + """ + private static native void $optionalJavaKitClass(java.lang.Long arg); + """ + ] + ) + } + + @Test + func optionalJavaKitClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2") + func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { + SwiftModule.optionalJavaKitClass(arg.map { + return JavaLong(javaThis: $0, environment: environment!) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:666 + ) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 09a8626d..01a2e3c0 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -205,7 +205,8 @@ struct JNIStructTests { func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 9d2fcb22..933e4f08 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -71,7 +71,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.constant.getJNIValue(in: environment!) @@ -134,7 +135,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.mutable.getJNIValue(in: environment!) @@ -145,7 +147,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) @@ -194,7 +197,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.computed.getJNIValue(in: environment!) @@ -243,7 +247,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } do { @@ -311,7 +316,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.getterAndSetter.getJNIValue(in: environment!) @@ -322,7 +328,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) @@ -385,7 +392,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.someBoolean.getJNIValue(in: environment!) @@ -396,7 +404,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) @@ -459,7 +468,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.isBoolean.getJNIValue(in: environment!) @@ -470,7 +480,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) From 68f695a60c456ecdf91863dd563bc29f2f09dc62 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 2 Aug 2025 14:59:06 +0200 Subject: [PATCH 118/178] Update Package.swift (#345) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 51608c31..bac55bd6 100644 --- a/Package.swift +++ b/Package.swift @@ -32,7 +32,7 @@ func findJavaHome() -> String { } - if ProcessInfo.processInfo.environment["SPI_BUILD"] == "1" { + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" { // just ignore that we're missing a JAVA_HOME when building in Swift Package Index return "" } From a3791cf7c94146a34f96fd9ad53efef9d58c2a1b Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 2 Aug 2025 22:36:21 -0400 Subject: [PATCH 119/178] [JavaKit] Add `JNIEnvPointer` and `JNINativeInterface_` for Android (#311) * [JavaKit] Add `JNIEnvPointer` for Android * [JavaKit] Fix `JNINativeInterface_` for Android --- Sources/JavaKit/JavaEnvironment.swift | 4 +++ .../JavaKitVM/JavaVirtualMachine.swift | 25 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/JavaKit/JavaEnvironment.swift index 4895b32c..422262da 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/JavaKit/JavaEnvironment.swift @@ -14,6 +14,10 @@ import JavaRuntime +#if canImport(Android) +typealias JNINativeInterface_ = JNINativeInterface +#endif + extension UnsafeMutablePointer { public var interface: JNINativeInterface_ { self.pointee!.pointee } } diff --git a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift b/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift index 0800a89e..edbddbb7 100644 --- a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift @@ -19,6 +19,11 @@ import Foundation #endif public typealias JavaVMPointer = UnsafeMutablePointer +#if canImport(Android) +typealias JNIEnvPointer = UnsafeMutablePointer +#else +typealias JNIEnvPointer = UnsafeMutableRawPointer +#endif public final class JavaVirtualMachine: @unchecked Sendable { /// The JNI version that we depend on. @@ -61,7 +66,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { ) throws { self.classpath = classpath var jvm: JavaVMPointer? = nil - var environment: UnsafeMutableRawPointer? = nil + var environment: JNIEnvPointer? = nil var vmArgs = JavaVMInitArgs() vmArgs.version = JavaVirtualMachine.jniVersion vmArgs.ignoreUnrecognized = jboolean(ignoreUnrecognized ? JNI_TRUE : JNI_FALSE) @@ -161,12 +166,18 @@ extension JavaVirtualMachine { return environment.assumingMemoryBound(to: JNIEnv?.self) } +#if canImport(Android) + var jniEnv = environment?.assumingMemoryBound(to: JNIEnv?.self) +#else + var jniEnv = environment +#endif + // Attach the current thread to the JVM. let attachResult: jint if asDaemon { - attachResult = jvm.pointee!.pointee.AttachCurrentThreadAsDaemon(jvm, &environment, nil) + attachResult = jvm.pointee!.pointee.AttachCurrentThreadAsDaemon(jvm, &jniEnv, nil) } else { - attachResult = jvm.pointee!.pointee.AttachCurrentThread(jvm, &environment, nil) + attachResult = jvm.pointee!.pointee.AttachCurrentThread(jvm, &jniEnv, nil) } // If we failed to attach, report that. @@ -175,9 +186,13 @@ extension JavaVirtualMachine { throw attachError } - JavaVirtualMachine.destroyTLS.set(environment!) + JavaVirtualMachine.destroyTLS.set(jniEnv!) - return environment!.assumingMemoryBound(to: JNIEnv?.self) +#if canImport(Android) + return jniEnv! +#else + return jniEnv!.assumingMemoryBound(to: JNIEnv?.self) +#endif } /// Detach the current thread from the Java Virtual Machine. All Java From 1ac73e4518f988741c62dba7c7c17a4655fd6e00 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Mon, 4 Aug 2025 16:57:47 +0200 Subject: [PATCH 120/178] Update Package.swift (#347) --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index bac55bd6..65737b2b 100644 --- a/Package.swift +++ b/Package.swift @@ -32,8 +32,8 @@ func findJavaHome() -> String { } - if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" { - // just ignore that we're missing a JAVA_HOME when building in Swift Package Index + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" && ProcessInfo.processInfo.environment["SPI_BUILD"] == nil { + // Just ignore that we're missing a JAVA_HOME when building in Swift Package Index during general processing where no Java is needed. However, do _not_ suppress the error during SPI's compatibility build stage where Java is required. return "" } fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") From 8d41e6e263b568a9a3adacee0077ac1dfc03762a Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 5 Aug 2025 17:09:16 -0700 Subject: [PATCH 121/178] [JExtract] Fix importing extensions (#351) --- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 4 +- .../ExtensionImportTests.swift | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Tests/JExtractSwiftTests/ExtensionImportTests.swift diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 3efddbfc..a933ff3e 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.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 @@ -84,7 +84,7 @@ final class Swift2JavaVisitor { } func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { - guard parent != nil else { + guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore return } diff --git a/Tests/JExtractSwiftTests/ExtensionImportTests.swift b/Tests/JExtractSwiftTests/ExtensionImportTests.swift new file mode 100644 index 00000000..2bad2c7b --- /dev/null +++ b/Tests/JExtractSwiftTests/ExtensionImportTests.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +final class ExtensionImportTests { + let interfaceFile = + """ + extension MyStruct { + public func methodInExtension() {} + } + + public struct MyStruct {} + """ + + @Test("Import extensions: Swift thunks") + func data_swiftThunk() throws { + try assertOutput( + input: interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + @_cdecl("swiftjava_getType_SwiftModule_MyStruct") + public func swiftjava_getType_SwiftModule_MyStruct() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(MyStruct.self, to: UnsafeMutableRawPointer.self) + } + """, + """ + @_cdecl("swiftjava_SwiftModule_MyStruct_methodInExtension") + public func swiftjava_SwiftModule_MyStruct_methodInExtension(_ self: UnsafeRawPointer) { + self.assumingMemoryBound(to: MyStruct.self).pointee.methodInExtension() + } + """ + ] + ) + } +} From 1e4c6ebd9aa7bd3bd095c9bd8b1f26634a016001 Mon Sep 17 00:00:00 2001 From: David Ko Date: Wed, 6 Aug 2025 19:12:49 -0400 Subject: [PATCH 122/178] replace Java2Swift.config with swift-java.config (#350) --- .../Documentation.docc/SwiftJavaCommandLineTool.md | 2 +- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 2 +- Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md index f83458d4..e8a3dfed 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -57,7 +57,7 @@ OPTIONS: The name of the Swift module into which the resulting Swift types will be generated. --depends-on 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 + e.g., JavaKitJar=Sources/JavaKitJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. --swift-native-implementation The names of Java classes whose declared native methods will be implemented in Swift. diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 54a99708..88fbf7f3 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -70,7 +70,7 @@ extension SwiftJava { @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, - e.g., Sources/JavaKitJar/Java2Swift.config. There should be one of these options + e.g., Sources/JavaKitJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. """ ) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 4c5bd97b..3e4a62af 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -40,7 +40,7 @@ extension SwiftJava { @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 + e.g., JavaKitJar=Sources/JavaKitJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. """ ) From f87fe2811359b70d5f1e6a2dc916e06b6802bd72 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 8 Aug 2025 00:13:13 +0200 Subject: [PATCH 123/178] [JExtract/JNI] Add `auto` arena to SwiftKitCore and add memory management options (#353) --- .../com/example/swift/HelloJava2SwiftJNI.java | 4 +- .../com/example/swift/MySwiftClassTest.java | 30 ++++---- .../com/example/swift/MySwiftStructTest.java | 7 +- .../java/com/example/swift/OptionalsTest.java | 6 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 32 ++++++-- .../Configuration.swift | 5 ++ .../GenerationMode.swift | 22 ++++++ .../Commands/JExtractCommand.swift | 9 +++ .../swiftkit/core/AutoSwiftMemorySession.java | 56 ++++++++++++++ .../core/ConfinedSwiftMemorySession.java | 6 +- .../swift/swiftkit/core/JNISwiftInstance.java | 9 +-- .../org/swift/swiftkit/core/SwiftArena.java | 13 +++- .../swiftkit/core/SwiftMemoryManagement.java | 19 +++++ .../swiftkit/core/ref/PhantomCleanable.java | 35 +++++++++ .../swift/swiftkit/core/ref/SwiftCleaner.java | 67 ++++++++++++++++ .../org/swift/swiftkit/AutoArenaTest.java | 57 ++++++++++++++ .../MemoryManagementModeTests.swift | 77 +++++++++++++++++++ 17 files changed, 411 insertions(+), 43 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java create mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java create mode 100644 Tests/JExtractSwiftTests/MemoryManagementModeTests.swift diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 21cad317..3109f64e 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -18,8 +18,8 @@ // Import javakit/swiftkit support libraries +import org.swift.swiftkit.core.SwiftArena; import org.swift.swiftkit.core.SwiftLibraries; -import org.swift.swiftkit.core.ConfinedSwiftMemorySession; public class HelloJava2SwiftJNI { @@ -41,7 +41,7 @@ static void examples() { MySwiftClass.method(); - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index e7de03ad..2e9a7e62 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -15,7 +15,7 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; import java.util.Optional; import java.util.OptionalInt; @@ -26,7 +26,7 @@ public class MySwiftClassTest { @Test void init_noParameters() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(arena); assertNotNull(c); } @@ -34,7 +34,7 @@ void init_noParameters() { @Test void init_withParameters() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(1337, 42, arena); assertNotNull(c); } @@ -42,7 +42,7 @@ void init_withParameters() { @Test void sum() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(30, c.sum()); } @@ -50,7 +50,7 @@ void sum() { @Test void xMultiplied() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.xMultiplied(10)); } @@ -58,7 +58,7 @@ void xMultiplied() { @Test void throwingFunction() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.throwingFunction()); @@ -68,7 +68,7 @@ void throwingFunction() { @Test void constant() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(100, c.getConstant()); } @@ -76,7 +76,7 @@ void constant() { @Test void mutable() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(0, c.getMutable()); c.setMutable(42); @@ -86,7 +86,7 @@ void mutable() { @Test void product() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.getProduct()); } @@ -94,7 +94,7 @@ void product() { @Test void throwingVariable() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.getThrowingVariable()); @@ -105,7 +105,7 @@ void throwingVariable() { @Test void mutableDividedByTwo() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(0, c.getMutableDividedByTwo()); c.setMutable(20); @@ -117,7 +117,7 @@ void mutableDividedByTwo() { @Test void isWarm() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertFalse(c.isWarm()); } @@ -125,7 +125,7 @@ void isWarm() { @Test void sumWithX() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c1 = MySwiftClass.init(20, 10, arena); MySwiftClass c2 = MySwiftClass.init(50, 10, arena); assertEquals(70, c1.sumX(c2)); @@ -134,7 +134,7 @@ void sumWithX() { @Test void copy() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c1 = MySwiftClass.init(20, 10, arena); MySwiftClass c2 = c1.copy(arena); @@ -146,7 +146,7 @@ void copy() { @Test void addXWithJavaLong() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c1 = MySwiftClass.init(20, 10, arena); Long javaLong = 50L; assertEquals(70, c1.addXWithJavaLong(javaLong)); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java index 9eeaf029..c2c1170b 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -16,13 +16,14 @@ import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; import static org.junit.jupiter.api.Assertions.*; public class MySwiftStructTest { @Test void init() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); assertEquals(1337, s.getCapacity()); assertEquals(42, s.getLen()); @@ -31,7 +32,7 @@ void init() { @Test void getAndSetLen() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); s.setLen(100); assertEquals(100, s.getLen()); @@ -40,7 +41,7 @@ void getAndSetLen() { @Test void increaseCap() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); long newCap = s.increaseCap(10); assertEquals(1347, newCap); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java index f7262ad4..d60ff6d5 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -15,7 +15,7 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; import java.util.Optional; import java.util.OptionalDouble; @@ -82,7 +82,7 @@ void optionalString() { @Test void optionalClass() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(arena); assertEquals(Optional.empty(), MySwiftLibrary.optionalClass(Optional.empty(), arena)); Optional optionalClass = MySwiftLibrary.optionalClass(Optional.of(c), arena); @@ -99,7 +99,7 @@ void optionalJavaKitLong() { @Test void multipleOptionals() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(arena); OptionalLong result = MySwiftLibrary.multipleOptionals( Optional.of((byte) 1), diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 0daf14de..0920a89b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -247,29 +247,47 @@ extension JNISwift2JavaGenerator { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } + let translatedSignature = translatedDecl.translatedFunctionSignature var modifiers = ["public"] + if decl.isStatic || decl.isInitializer || !decl.hasParent { modifiers.append("static") } - let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map({ $0.parameter.renderParameter() }) - if translatedSignature.requiresSwiftArena { - parameters.append("SwiftArena swiftArena$") - } + var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } let throwsClause = decl.isThrowing ? " throws Exception" : "" var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") if !annotationsStr.isEmpty { annotationsStr += "\n" } - let modifiersStr = modifiers.joined(separator: " ") let parametersStr = parameters.joined(separator: ", ") + // Print default global arena variation + if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { + printDeclDocumentation(&printer, decl) + printer.printBraceBlock( + "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + ) { printer in + let globalArenaName = "SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA" + let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [globalArenaName] + let call = "\(translatedDecl.name)(\(arguments.joined(separator: ", ")))" + if translatedDecl.translatedFunctionSignature.resultType.javaType.isVoid { + printer.print("\(call);") + } else { + printer.print("return \(call);") + } + } + printer.println() + } + + if translatedSignature.requiresSwiftArena { + parameters.append("SwiftArena swiftArena$") + } printDeclDocumentation(&printer, decl) printer.printBraceBlock( - "\(annotationsStr)\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" ) { printer in printDowncall(&printer, decl) } diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 0ff089da..2d9b4311 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -51,6 +51,11 @@ public struct Configuration: Codable { minimumInputAccessLevelMode ?? .default } + public var memoryManagementMode: JExtractMemoryManagementMode? + public var effectiveMemoryManagementMode: JExtractMemoryManagementMode { + memoryManagementMode ?? .default + } + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index 1feac411..22fdd5f5 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -76,3 +76,25 @@ extension JExtractMinimumAccessLevelMode { .public } } + + +/// Configures how memory should be managed by the user +public enum JExtractMemoryManagementMode: String, Codable { + /// Force users to provide an explicit `SwiftArena` to all calls that require them. + case explicit + + /// Provide both explicit `SwiftArena` support + /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. + case allowGlobalAutomatic + + public static var `default`: Self { + .explicit + } + + public var requiresGlobalArena: Bool { + switch self { + case .explicit: false + case .allowGlobalAutomatic: true + } + } +} diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 88fbf7f3..b32f34a0 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -67,6 +67,9 @@ extension SwiftJava { @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allow-automatic`, user can omit this parameter and a global GC-based arena will be used. `force-automatic` removes all explicit memory management.") + var memoryManagementMode: JExtractMemoryManagementMode = .default + @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, @@ -89,6 +92,7 @@ extension SwiftJava.JExtractCommand { config.writeEmptyFiles = writeEmptyFiles config.unsignedNumbersMode = unsignedNumbers config.minimumInputAccessLevelMode = minimumInputAccessLevel + config.memoryManagementMode = memoryManagementMode try checkModeCompatibility() @@ -117,6 +121,10 @@ extension SwiftJava.JExtractCommand { case .wrapGuava: () // OK } + } else if self.mode == .ffm { + guard self.memoryManagementMode == .explicit else { + throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode)' memory management mode! \(Self.helpMessage)") + } } } } @@ -148,3 +156,4 @@ struct IllegalModeCombinationError: Error { extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} +extension JExtractMemoryManagementMode: ExpressibleByArgument {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java new file mode 100644 index 00000000..36e73209 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + + +import org.swift.swiftkit.core.ref.SwiftCleaner; + +import java.util.Objects; +import java.util.concurrent.ThreadFactory; + +/** + * A memory session which manages registered objects via the Garbage Collector. + * + *

When registered Java wrapper classes around native Swift instances {@link SwiftInstance}, + * are eligible for collection, this will trigger the cleanup of the native resources as well. + * + *

This memory session is LESS reliable than using a {@link ConfinedSwiftMemorySession} because + * the timing of when the native resources are cleaned up is somewhat undefined, and rely on the + * system GC. Meaning, that if an object nas been promoted to an old generation, there may be a + * long time between the resource no longer being referenced "in Java" and its native memory being released, + * and also the deinit of the Swift type being run. + * + *

This can be problematic for Swift applications which rely on quick release of resources, and may expect + * the deinits to run in expected and "quick" succession. + * + *

Whenever possible, prefer using an explicitly managed {@link SwiftArena}, such as {@link SwiftArena#ofConfined()}. + */ +final class AutoSwiftMemorySession implements SwiftArena { + private final SwiftCleaner swiftCleaner; + + public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { + this.swiftCleaner = SwiftCleaner.create(cleanerThreadFactory); + } + + @Override + public void register(SwiftInstance instance) { + Objects.requireNonNull(instance, "value"); + + // We make sure we don't capture `instance` in the + // cleanup action, so we can ignore the warning below. + var cleanupAction = instance.$createCleanup(); + swiftCleaner.register(instance, cleanupAction); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 7c6e80fb..4383a6fe 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -28,17 +28,13 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final ConfinedResourceList resources; - public ConfinedSwiftMemorySession() { - this(Thread.currentThread()); - } - public ConfinedSwiftMemorySession(Thread owner) { this.owner = owner; this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); } - public void checkValid() throws RuntimeException { + void checkValid() throws RuntimeException { if (this.owner != null && this.owner != Thread.currentThread()) { throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); } else if (this.state.get() < ACTIVE) { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index 6b30ed2f..95f1e5a0 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -56,13 +56,8 @@ protected JNISwiftInstance(long selfPointer, SwiftArena arena) { @Override public SwiftInstanceCleanup $createCleanup() { - final AtomicBoolean statusDestroyedFlag = $statusDestroyedFlag(); - Runnable markAsDestroyed = new Runnable() { - @Override - public void run() { - statusDestroyedFlag.set(true); - } - }; + var statusDestroyedFlag = $statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); return new JNISwiftInstanceCleanup(this.$createDestroyFunction(), markAsDestroyed); } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index ed16d250..3b6c4626 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -14,6 +14,8 @@ package org.swift.swiftkit.core; +import java.util.concurrent.ThreadFactory; + /** * A Swift arena manages Swift allocated memory for classes, structs, enums etc. * When an arena is closed, it will destroy all managed swift objects in a way appropriate to their type. @@ -21,12 +23,21 @@ *

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 { /** * Register a Swift object. * Its memory should be considered managed by this arena, and be destroyed when the arena is closed. */ void register(SwiftInstance instance); + + static ClosableSwiftArena ofConfined() { + return new ConfinedSwiftMemorySession(Thread.currentThread()); + } + + static SwiftArena ofAuto() { + ThreadFactory cleanerThreadFactory = r -> new Thread(r, "AutoSwiftArenaCleanerThread"); + return new AutoSwiftMemorySession(cleanerThreadFactory); + } } /** diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java new file mode 100644 index 00000000..2b9a1209 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +public class SwiftMemoryManagement { + public static final SwiftArena GLOBAL_SWIFT_JAVA_ARENA = SwiftArena.ofAuto(); +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java new file mode 100644 index 00000000..2efcdae7 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.ref; + +import java.lang.ref.PhantomReference; + +public class PhantomCleanable extends PhantomReference { + private final Runnable cleanupAction; + private final SwiftCleaner swiftCleaner; + + public PhantomCleanable(Object referent, SwiftCleaner swiftCleaner, Runnable cleanupAction) { + super(referent, swiftCleaner.referenceQueue); + this.cleanupAction = cleanupAction; + this.swiftCleaner = swiftCleaner; + swiftCleaner.list.add(this); + } + + public void cleanup() { + if (swiftCleaner.list.remove(this)) { + cleanupAction.run(); + } + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java new file mode 100644 index 00000000..2a5b49f5 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.ref; + +import java.lang.ref.ReferenceQueue; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ThreadFactory; + +public class SwiftCleaner implements Runnable { + final ReferenceQueue referenceQueue; + final List list; + + private SwiftCleaner() { + this.referenceQueue = new ReferenceQueue<>(); + this.list = Collections.synchronizedList(new LinkedList<>()); + } + + public static SwiftCleaner create(ThreadFactory threadFactory) { + SwiftCleaner swiftCleaner = new SwiftCleaner(); + swiftCleaner.start(threadFactory); + return swiftCleaner; + } + + void start(ThreadFactory threadFactory) { + // This makes sure the linked list is not empty when the thread starts, + // and the thread will run at least until the cleaner itself can be GCed. + new PhantomCleanable(this, this, () -> {}); + + Thread thread = threadFactory.newThread(this); + thread.setDaemon(true); + thread.start(); + } + + public void register(Object resourceHolder, Runnable cleaningAction) { + Objects.requireNonNull(resourceHolder, "resourceHolder"); + Objects.requireNonNull(cleaningAction, "cleaningAction"); + new PhantomCleanable(resourceHolder, this, cleaningAction); + } + + @Override + public void run() { + while (!list.isEmpty()) { + try { + PhantomCleanable removed = (PhantomCleanable) referenceQueue.remove(60 * 1000L); + removed.cleanup(); + } catch (Throwable e) { + // ignore exceptions from the cleanup action + // (including interruption of cleanup thread) + } + } + } +} diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java new file mode 100644 index 00000000..b414962f --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.JNISwiftInstance; +import org.swift.swiftkit.core.SwiftArena; + +public class AutoArenaTest { + + @Test + @SuppressWarnings("removal") // System.runFinalization() will be removed + public void cleaner_releases_native_resource() { + SwiftArena arena = SwiftArena.ofAuto(); + + // This object is registered to the arena. + var object = new FakeSwiftInstance(arena); + var statusDestroyedFlag = object.$statusDestroyedFlag(); + + // Release the object and hope it gets GC-ed soon + + // noinspection UnusedAssignment + object = null; + + var i = 1_000; + while (!statusDestroyedFlag.get()) { + System.runFinalization(); + System.gc(); + + if (i-- < 1) { + throw new RuntimeException("Reference was not cleaned up! Did Cleaner not pick up the release?"); + } + } + } + + private static class FakeSwiftInstance extends JNISwiftInstance { + public FakeSwiftInstance(SwiftArena arena) { + super(1, arena); + } + + protected Runnable $createDestroyFunction() { + return () -> {}; + } + } +} diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift new file mode 100644 index 00000000..4edf59c2 --- /dev/null +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.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 JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +@Suite +struct MemoryManagementModeTests { + let text = + """ + class MyClass {} + + public func f() -> MyClass + """ + + @Test + func explicit() throws { + var config = Configuration() + config.memoryManagementMode = .explicit + + try assertOutput( + input: text, + config: config, + .jni, .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func f() -> MyClass + * } + */ + public static MyClass f(SwiftArena swiftArena$) { + return new MyClass(SwiftModule.$f(), swiftArena$); + } + """, + ] + ) + } + + @Test + func allowGlobalAutomatic() throws { + var config = Configuration() + config.memoryManagementMode = .allowGlobalAutomatic + + try assertOutput( + input: text, + config: config, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static MyClass f() { + return f(SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA); + } + """, + """ + public static MyClass f(SwiftArena swiftArena$) { + return new MyClass(SwiftModule.$f(), swiftArena$); + } + """, + ] + ) + } +} From d5f9c9059665550d4b696f2a345ba085bfdd11e9 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 8 Aug 2025 07:13:28 +0900 Subject: [PATCH 124/178] Update Dockerfile (#349) --- docker/Dockerfile | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 06b17a87..c3568b54 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,9 +18,13 @@ ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US.UTF-8 -COPY install_jdk.sh . - # JDK dependency -RUN bash -xc 'JDK_VENDOR=corretto ./install_jdk.sh' -ENV JAVA_HOME="/usr/lib/jvm/default-jdk" -ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" +RUN curl -s "https://get.sdkman.io" | bash +RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 24.0.1-amzn" + +RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ + tar zxf swiftly-$(uname -m).tar.gz && \ + ./swiftly init --quiet-shell-followup --assume-yes && \ + . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \ + hash -r + From 5dd3f36fa086339e1ff90bcad7cb433d0560a125 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 13 Aug 2025 11:54:56 +0900 Subject: [PATCH 125/178] gh: test with nightly swift builds (#357) --- .github/workflows/pull_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b2338ed2..89ca4289 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -157,7 +157,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2'] # no nightly checks for macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From 69e487ba1871ff9f2f384ae9e0a547616dff128d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 14 Aug 2025 18:28:23 +0900 Subject: [PATCH 126/178] jextract: hide memory location wrapping constructor, give it proper name (#360) --- .../JExtractSwiftLib/FFM/ConversionStep.swift | 2 +- ...wift2JavaGenerator+JavaBindingsPrinting.swift | 13 +++++++++++-- .../FFMSwift2JavaGenerator+JavaTranslation.swift | 5 ++++- .../FFM/FFMSwift2JavaGenerator.swift | 15 ++++++++++++++- ...wift2JavaGenerator+JavaBindingsPrinting.swift | 15 ++++++++++++++- .../JNISwift2JavaGenerator+JavaTranslation.swift | 13 ++++++++++--- Tests/JExtractSwiftTests/DataImportTests.swift | 7 ++++--- Tests/JExtractSwiftTests/JNI/JNIClassTests.swift | 16 +++++++++++----- .../JNI/JNIOptionalTests.swift | 2 +- .../JExtractSwiftTests/JNI/JNIStructTests.swift | 12 +++++++++--- .../MemoryManagementModeTests.swift | 4 ++-- Tests/JExtractSwiftTests/MethodImportTests.swift | 6 +++--- 12 files changed, 84 insertions(+), 26 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index 656d9dd9..f59f739d 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -123,7 +123,7 @@ enum ConversionStep: Equatable { // 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(placeholder: "\(placeholder)_\(index)", bodyItems: &bodyItems)!.description diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index b8749299..84a90275 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -407,6 +407,7 @@ extension FFMSwift2JavaGenerator { "arena$" } + // FIXME: use trailing$ convention let varName = outParameter.name.isEmpty ? "_result" : "_result_" + outParameter.name printer.print( @@ -463,7 +464,7 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { switch self { case .placeholder, .explodedName, .constant, .readMemorySegment: return false - case .constructSwiftValue: + case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true case .call(let inner, _, _), .cast(let inner, _), .construct(let inner, _), @@ -482,7 +483,11 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { return false case .readMemorySegment: return true - case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), .swiftValueSelfSegment(let inner): + case .cast(let inner, _), + .construct(let inner, _), + .constructSwiftValue(let inner, _), + .swiftValueSelfSegment(let inner), + .wrapMemoryAddressUnsafe(let inner, _): return inner.requiresSwiftArena case .call(let inner, _, let withArena): return withArena || inner.requiresTemporaryArena @@ -522,6 +527,10 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" + case .wrapMemoryAddressUnsafe(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" + case .construct(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType)(\(inner))" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index f8b2e7e8..06a4d171 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -684,7 +684,7 @@ extension FFMSwift2JavaGenerator { outParameters: [ JavaParameter(name: "", type: javaType) ], - conversion: .constructSwiftValue(.placeholder, javaType) + conversion: .wrapMemoryAddressUnsafe(.placeholder, javaType) ) case .tuple: @@ -732,6 +732,9 @@ extension FFMSwift2JavaGenerator { // Call 'new \(Type)(\(placeholder), swiftArena$)'. indirect case constructSwiftValue(JavaConversionStep, JavaType) + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type + indirect case wrapMemoryAddressUnsafe(JavaConversionStep, JavaType) + // Construct the type using the placeholder as arguments. indirect case construct(JavaConversionStep, JavaType) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 3e60db14..c66b0029 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -216,9 +216,22 @@ extension FFMSwift2JavaGenerator { printer.print( """ - public \(decl.swiftNominal.name)(MemorySegment segment, AllocatingSwiftArena arena) { + private \(decl.swiftNominal.name)(MemorySegment segment, AllocatingSwiftArena arena) { super(segment, arena); } + + /** + * Assume that the passed {@code MemorySegment} represents a memory address of a {@link \(decl.swiftNominal.name)}. + *

+ * Warnings: + *

    + *
  • No checks are performed about the compatibility of the pointed at memory and the actual \(decl.swiftNominal.name) types.
  • + *
  • This operation does not copy, or retain, the pointed at pointer, so its lifetime must be ensured manually to be valid when wrapping.
  • + *
+ */ + public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(MemorySegment selfPointer, AllocatingSwiftArena swiftArena) { + return new \(decl.swiftNominal.name)(selfPointer, swiftArena); + } """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 0920a89b..d3452cef 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -117,9 +117,22 @@ extension JNISwift2JavaGenerator { printer.print( """ - public \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { + private \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { super(selfPointer, swiftArena); } + + /** + * Assume that the passed {@code long} represents a memory address of a {@link \(decl.swiftNominal.name)}. + *

+ * Warnings: + *

    + *
  • No checks are performed about the compatibility of the pointed at memory and the actual \(decl.swiftNominal.name) types.
  • + *
  • This operation does not copy, or retain, the pointed at pointer, so its lifetime must be ensured manually to be valid when wrapping.
  • + *
+ */ + public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new \(decl.swiftNominal.name)(selfPointer, swiftArena); + } """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 0db77ece..64ae23b7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -383,7 +383,7 @@ extension JNISwift2JavaGenerator { javaType: javaType, annotations: resultAnnotations, outParameters: [], - conversion: .constructSwiftValue(.placeholder, javaType) + conversion: .wrapMemoryAddressUnsafe(.placeholder, javaType) ) case .tuple([]): @@ -465,7 +465,7 @@ extension JNISwift2JavaGenerator { discriminatorName: "result_discriminator$", optionalClass: "Optional", javaType: .long, - toValue: .constructSwiftValue(.placeholder, .class(package: nil, name: nominalTypeName)) + toValue: .wrapMemoryAddressUnsafe(.placeholder, .class(package: nil, name: nominalTypeName)) ) ) @@ -582,6 +582,9 @@ extension JNISwift2JavaGenerator { /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type + indirect case wrapMemoryAddressUnsafe(JavaNativeConversionStep, JavaType) + indirect case call(JavaNativeConversionStep, function: String) indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) @@ -641,6 +644,10 @@ extension JNISwift2JavaGenerator { case .constructSwiftValue(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" + + case .wrapMemoryAddressUnsafe(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" case .call(let inner, let function): let inner = inner.render(&printer, placeholder) @@ -705,7 +712,7 @@ extension JNISwift2JavaGenerator { case .placeholder, .constant, .isOptionalPresent: return false - case .constructSwiftValue: + case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true case .valueMemoryAddress(let inner): diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index 7416779d..cf4ed387 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -86,7 +86,7 @@ final class DataImportTests { ] ) } - + @Test("Import Data: JavaBindings") func data_javaBindings() throws { @@ -167,7 +167,7 @@ final class DataImportTests { public static Data returnData(AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); swiftjava_SwiftModule_returnData.call(_result); - return new Data(_result, swiftArena$); + return Data.wrapMemoryAddressUnsafe(_result, swiftArena$); } """, @@ -210,7 +210,7 @@ final class DataImportTests { public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result); - return new Data(_result, swiftArena$); + return Data.wrapMemoryAddressUnsafe(_result, swiftArena$); } """, @@ -408,4 +408,5 @@ final class DataImportTests { ] ) } + } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 5b015f89..1e33efcb 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -66,11 +66,17 @@ struct JNIClassTests { System.loadLibrary(LIB_NAME); return true; } - - public MyClass(long selfPointer, SwiftArena swiftArena) { + """, + """ + private MyClass(long selfPointer, SwiftArena swiftArena) { super(selfPointer, swiftArena); } """, + """ + public static MyClass wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new MyClass(selfPointer, swiftArena); + } + """ ]) try assertOutput( input: source, @@ -164,7 +170,7 @@ struct JNIClassTests { * } */ public static MyClass init(long x, long y, SwiftArena swiftArena$) { - return new MyClass(MyClass.$init(x, y), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(x, y), swiftArena$); } """, """ @@ -175,7 +181,7 @@ struct JNIClassTests { * } */ public static MyClass init(SwiftArena swiftArena$) { - return new MyClass(MyClass.$init(), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(), swiftArena$); } """, """ @@ -309,7 +315,7 @@ struct JNIClassTests { * } */ public MyClass copy(SwiftArena swiftArena$) { - return new MyClass(MyClass.$copy(this.$memoryAddress()), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(MyClass.$copy(this.$memoryAddress()), swiftArena$); } """, """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index cd04660b..f9c70ba4 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -158,7 +158,7 @@ struct JNIOptionalTests { public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { byte[] result_discriminator$ = new byte[1]; long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(new MyClass(result$, swiftArena$)) : Optional.empty(); + return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); } """, """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 01a2e3c0..59d7ee6b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -56,10 +56,16 @@ struct JNIStructTests { System.loadLibrary(LIB_NAME); return true; } - - public MyStruct(long selfPointer, SwiftArena swiftArena) { + """, + """ + private MyStruct(long selfPointer, SwiftArena swiftArena) { super(selfPointer, swiftArena); } + """, + """ + public static MyStruct wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new MyStruct(selfPointer, swiftArena); + } """ ]) try assertOutput( @@ -110,7 +116,7 @@ struct JNIStructTests { * } */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { - return new MyStruct(MyStruct.$init(x, y), swiftArena$); + return MyStruct.wrapMemoryAddressUnsafe(MyStruct.$init(x, y), swiftArena$); } """, """ diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 4edf59c2..e64d4397 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -43,7 +43,7 @@ struct MemoryManagementModeTests { * } */ public static MyClass f(SwiftArena swiftArena$) { - return new MyClass(SwiftModule.$f(), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); } """, ] @@ -68,7 +68,7 @@ struct MemoryManagementModeTests { """, """ public static MyClass f(SwiftArena swiftArena$) { - return new MyClass(SwiftModule.$f(), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); } """, ] diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index fd885f1b..3aa7d394 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -227,7 +227,7 @@ final class MethodImportTests { public static MySwiftClass globalReturnClass(AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_globalReturnClass.call(_result); - return new MySwiftClass(_result, swiftArena$); + return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) @@ -404,7 +404,7 @@ final class MethodImportTests { public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result) - return new MySwiftClass(_result, swiftArena$); + return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) @@ -449,7 +449,7 @@ final class MethodImportTests { public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT); swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result) - return new MySwiftStruct(_result, swiftArena$); + return MySwiftStruct.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) From a720622d62b3c7f2178f8b8f61e2594fb3cd1958 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 15 Aug 2025 11:15:43 +0900 Subject: [PATCH 127/178] fix javadoc formatting (#361) --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 10 +-- .../JNI/JNIClassTests.swift | 40 +++++------ .../JNI/JNIClosureTests.swift | 20 +++--- .../JNI/JNIJavaKitTests.swift | 10 +-- .../JNI/JNIModuleTests.swift | 60 ++++++++-------- .../JNI/JNIOptionalTests.swift | 68 +++++++++---------- .../JNI/JNIStructTests.swift | 20 +++--- .../JNI/JNIUnsignedNumberTests.swift | 40 +++++------ .../MemoryManagementModeTests.swift | 16 ++--- 9 files changed, 142 insertions(+), 142 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index d3452cef..6d82175f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -369,11 +369,11 @@ extension JNISwift2JavaGenerator { printer.print( """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ """ ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 1e33efcb..30bb7433 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -164,22 +164,22 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public init(x: Int64, y: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ public static MyClass init(long x, long y, SwiftArena swiftArena$) { return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(x, y), swiftArena$); } """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public init() - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public init() + * } + */ public static MyClass init(SwiftArena swiftArena$) { return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(), swiftArena$); } @@ -309,11 +309,11 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func copy() -> MyClass - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func copy() -> MyClass + * } + */ public MyClass copy(SwiftArena swiftArena$) { return MyClass.wrapMemoryAddressUnsafe(MyClass.$copy(this.$memoryAddress()), swiftArena$); } @@ -361,11 +361,11 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func isEqual(to other: MyClass) -> Bool - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func isEqual(to other: MyClass) -> Bool + * } + */ public boolean isEqual(MyClass other) { return MyClass.$isEqual(other.$memoryAddress(), this.$memoryAddress()); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 9a388da1..b374d24e 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -36,11 +36,11 @@ struct JNIClosureTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func emptyClosure(closure: () -> ()) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func emptyClosure(closure: () -> ()) + * } + */ public static void emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure) { SwiftModule.$emptyClosure(closure); } @@ -88,11 +88,11 @@ struct JNIClosureTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) + * } + */ public static void closureWithArgumentsAndReturn(com.example.swift.SwiftModule.closureWithArgumentsAndReturn.closure closure) { SwiftModule.$closureWithArgumentsAndReturn(closure); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index 1f19c8f9..ac6b8384 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -37,11 +37,11 @@ struct JNIJavaKitTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) + * } + */ public static void function(java.lang.Long javaLong, java.lang.Integer javaInteger, long int) { SwiftModule.$function(javaLong, javaInteger, int); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 198276ba..ad55f491 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -67,11 +67,11 @@ struct JNIModuleTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func helloWorld() - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func helloWorld() + * } + */ public static void helloWorld() { SwiftModule.$helloWorld(); } @@ -81,11 +81,11 @@ struct JNIModuleTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 + * } + */ @Unsigned public static char takeIntegers(byte i1, short i2, int i3, long i4) { return SwiftModule.$takeIntegers(i1, i2, i3, i4); @@ -96,11 +96,11 @@ struct JNIModuleTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func otherPrimitives(b: Bool, f: Float, d: Double) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func otherPrimitives(b: Bool, f: Float, d: Double) + * } + */ public static void otherPrimitives(boolean b, float f, double d) { SwiftModule.$otherPrimitives(b, f, d); } @@ -151,11 +151,11 @@ struct JNIModuleTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func copy(_ string: String) -> String - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func copy(_ string: String) -> String + * } + */ public static java.lang.String copy(java.lang.String string) { return SwiftModule.$copy(string); } @@ -194,11 +194,11 @@ struct JNIModuleTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func methodA() throws - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func methodA() throws + * } + */ public static void methodA() throws Exception { SwiftModule.$methodA(); } @@ -208,11 +208,11 @@ struct JNIModuleTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func methodB() throws -> Int64 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func methodB() throws -> Int64 + * } + */ public static long methodB() throws Exception { return SwiftModule.$methodB(); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index f9c70ba4..2186e227 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -46,12 +46,12 @@ struct JNIOptionalTests { * public func optionalSugar(_ arg: Int64?) -> Int32? * } */ - public static OptionalInt optionalSugar(OptionalLong arg) { - long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); - byte discriminator$ = (byte) (combined$ & 0xFF); - int value$ = (int) (combined$ >> 32); - return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); - } + public static OptionalInt optionalSugar(OptionalLong arg) { + long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); + byte discriminator$ = (byte) (combined$ & 0xFF); + int value$ = (int) (combined$ >> 32); + return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); + } """, """ private static native long $optionalSugar(byte arg_discriminator, long arg_value); @@ -92,16 +92,16 @@ struct JNIOptionalTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func optionalExplicit(_ arg: Optional) -> Optional - * } - */ - public static Optional optionalExplicit(Optional arg) { - byte[] result_discriminator$ = new byte[1]; - java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalExplicit(_ arg: Optional) -> Optional + * } + */ + public static Optional optionalExplicit(Optional arg) { + byte[] result_discriminator$ = new byte[1]; + java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); + } """, """ private static native java.lang.String $optionalExplicit(byte arg_discriminator, java.lang.String arg_value, byte[] result_discriminator$); @@ -150,16 +150,16 @@ struct JNIOptionalTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func optionalClass(_ arg: MyClass?) -> MyClass? - * } - */ - public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { - byte[] result_discriminator$ = new byte[1]; - long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalClass(_ arg: MyClass?) -> MyClass? + * } + */ + public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { + byte[] result_discriminator$ = new byte[1]; + long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); + } """, """ private static native long $optionalClass(long arg, byte[] result_discriminator$); @@ -213,14 +213,14 @@ struct JNIOptionalTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func optionalJavaKitClass(_ arg: JavaLong?) - * } - */ - public static void optionalJavaKitClass(Optional arg) { - SwiftModule.$optionalJavaKitClass(arg.orElse(null)); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalJavaKitClass(_ arg: JavaLong?) + * } + */ + public static void optionalJavaKitClass(Optional arg) { + SwiftModule.$optionalJavaKitClass(arg.orElse(null)); + } """, """ private static native void $optionalJavaKitClass(java.lang.Long arg); diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 59d7ee6b..7f54fa7e 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -110,11 +110,11 @@ struct JNIStructTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public init(x: Int64, y: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { return MyStruct.wrapMemoryAddressUnsafe(MyStruct.$init(x, y), swiftArena$); } @@ -182,11 +182,11 @@ struct JNIStructTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func doSomething(x: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ public void doSomething(long x) { MyStruct.$doSomething(x, this.$memoryAddress()); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index f4dbffed..bfae447a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -27,11 +27,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func unsignedChar(_ arg: UInt16) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedChar(_ arg: UInt16) + * } + */ public static void unsignedChar(@Unsigned char arg) { SwiftModule.$unsignedChar(arg); } @@ -57,11 +57,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func unsignedInt(_ arg: UInt32) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedInt(_ arg: UInt32) + * } + */ public static void unsignedInt(@Unsigned int arg) { SwiftModule.$unsignedInt(arg); } @@ -83,11 +83,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func returnUnsignedInt() -> UInt32 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnUnsignedInt() -> UInt32 + * } + */ @Unsigned public static int returnUnsignedInt() { return SwiftModule.$returnUnsignedInt(); @@ -137,11 +137,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func unsignedLong(first: UInt64, second: UInt32) -> UInt32 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedLong(first: UInt64, second: UInt32) -> UInt32 + * } + */ @Unsigned public static int unsignedLong(@Unsigned long first, @Unsigned int second) { return SwiftModule.$unsignedLong(first, second); diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index e64d4397..3ce839b9 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -37,14 +37,14 @@ struct MemoryManagementModeTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func f() -> MyClass - * } - */ - public static MyClass f(SwiftArena swiftArena$) { - return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func f() -> MyClass + * } + */ + public static MyClass f(SwiftArena swiftArena$) { + return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); + } """, ] ) From 94cb7ecdf905ff1450de1d44b6f145460807020e Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 15 Aug 2025 04:59:20 +0200 Subject: [PATCH 128/178] Add "..." wildcard syntax to output assertions in JExtract tests (#362) Co-authored-by: Konrad `ktoso` Malawski --- .../Asserts/TextAssertions.swift | 65 +++++++++++++---- .../JNI/JNIVariablesTests.swift | 72 ++++--------------- 2 files changed, 64 insertions(+), 73 deletions(-) diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index e975d223..1f768fb2 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -124,18 +124,49 @@ func assertOutput( continue } - for (no, (g, e)) in zip(gotLines.dropFirst(matchingOutputOffset), expectedLines).enumerated() { - if g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 - || e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 { - continue - } + var currentExpectedLine: Int = 0 + var currentGotLine: Int = matchingOutputOffset + + outer: while currentExpectedLine < expectedLines.count { + let expectedLine = expectedLines[currentExpectedLine].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - let ge = g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - let ee = e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - if ge.commonPrefix(with: ee) != ee { - diffLineNumbers.append(no + matchingOutputOffset) + if expectedLine == "..." { + // Ignore the rest of output, if last line is placeholder. + guard currentExpectedLine != (expectedLines.count - 1) else { + break + } - #expect(ge == ee, sourceLocation: sourceLocation) + let nextLine = expectedLines[currentExpectedLine + 1].trimmingCharacters(in: .whitespacesAndNewlines) + while currentGotLine < gotLines.count { + let gottenLine = gotLines[currentGotLine].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if gottenLine.commonPrefix(with: nextLine) == nextLine { + currentExpectedLine += 2 + break + } else if nextLine == "..." { + // Skip any following "..." + currentExpectedLine += 1 + break + } else { + currentGotLine += 1 + } + + if currentGotLine == gotLines.count { + diffLineNumbers.append(currentExpectedLine + matchingOutputOffset + 1) + Issue.record("Expected to find '\(nextLine)' after wildcard, but end of file reached.", sourceLocation: sourceLocation) + break outer + } + } + + currentGotLine += 1 + } else { + let gottenLine = gotLines[currentGotLine].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if gottenLine.commonPrefix(with: expectedLine) != expectedLine { + diffLineNumbers.append(currentExpectedLine + matchingOutputOffset) + + #expect(gottenLine == expectedLine, sourceLocation: sourceLocation) + } + currentGotLine += 1 + currentExpectedLine += 1 } } @@ -155,9 +186,17 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Got output:") let printFromLineNo = matchingOutputOffset - let printToLineNo = matchingOutputOffset + expectedLines.count - for (n, g) in gotLines.enumerated() where n >= printFromLineNo && n <= printToLineNo { - print("\(n): \(g)".red(if: diffLineNumbers.contains(n))) + for (n, g) in gotLines.enumerated() where n >= printFromLineNo { + let baseLine = "\(n): \(g)" + var line = baseLine + if diffLineNumbers.contains(n) { + line += "\n" + let leadingCount = "\(n): ".count + let message = "\(String(repeating: " ", count: leadingCount))\(String(repeating: "^", count: 8)) EXPECTED MATCH OR SEARCHING FROM HERE " + line += "\(message)\(String(repeating: "^", count: max(0, line.count - message.count)))" + line = line.red + } + print(line) } print("==== ---------------------------------------------------------------\n") } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 933e4f08..c9d313a5 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -69,12 +69,7 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.constant.getJNIValue(in: environment!) } """ @@ -134,11 +129,12 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + ... let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } + ... return self$.pointee.mutable.getJNIValue(in: environment!) } """, @@ -146,11 +142,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) } """ @@ -195,12 +187,7 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.computed.getJNIValue(in: environment!) } """, @@ -245,12 +232,7 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... do { return try self$.pointee.computedThrowing.getJNIValue(in: environment!) } catch { @@ -314,24 +296,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.getterAndSetter.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) } """ @@ -390,24 +362,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.someBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) } """ @@ -466,24 +428,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.isBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) } """ From c3ecd548a85e8b1f2d809399e7b5f10e4215c9e3 Mon Sep 17 00:00:00 2001 From: David Ko Date: Thu, 14 Aug 2025 23:00:24 -0400 Subject: [PATCH 129/178] Add function documentation (#363) Co-authored-by: Konrad `ktoso` Malawski --- .gitignore | 3 +++ .../Commands/ResolveCommand.swift | 25 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 28dfbae2..d4e1c5ec 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .DS_Store .build .idea +.vscode Packages xcuserdata/ DerivedData/ @@ -16,6 +17,7 @@ bin/ BuildLogic/out/ .index-build .build-vscode +**/.vscode # Ignore gradle build artifacts .gradle @@ -45,3 +47,4 @@ Package.resolved */**/*.swiftdeps */**/*.swiftdeps~ */**/.docc-build/ + diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 3eb01fe7..c492e788 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -89,6 +89,15 @@ extension SwiftJava.ResolveCommand { resolvedClasspath: dependenciesClasspath) } + + /// Resolves Java dependencies from swift-java.config and returns classpath information. + /// + /// - Parameters: + /// - swiftModule: module name from --swift-module. e.g.: --swift-module MySwiftModule + /// - dependencies: parsed maven-style dependency descriptors (groupId:artifactId:version) + /// from Sources/MySwiftModule/swift-java.config "dependencies" array. + /// + /// - Throws: func resolveDependencies( swiftModule: String, dependencies: [JavaDependencyDescriptor] ) async throws -> ResolvedDependencyClasspath { @@ -108,6 +117,11 @@ extension SwiftJava.ResolveCommand { return ResolvedDependencyClasspath(for: dependencies, classpath: dependenciesClasspath) } + + /// Resolves maven-style dependencies from swift-java.config under temporary project directory. + /// + /// - Parameter dependencies: maven-style dependencies to resolve + /// - Returns: Colon-separated classpath func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String { let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) .appendingPathComponent(".build") @@ -158,6 +172,7 @@ extension SwiftJava.ResolveCommand { } } + /// Creates Gradle project files (build.gradle, settings.gradle.kts) in temporary directory. func printGradleProject(directory: URL, dependencies: [JavaDependencyDescriptor]) throws { let buildGradle = directory .appendingPathComponent("build.gradle", isDirectory: false) @@ -189,7 +204,14 @@ extension SwiftJava.ResolveCommand { """ try settingsGradleText.write(to: settingsGradle, atomically: true, encoding: .utf8) } - + + /// Creates {MySwiftModule}.swift.classpath in the --output-directory. + /// + /// - Parameters: + /// - swiftModule: Swift module name for classpath filename (--swift-module value) + /// - outputDirectory: Directory path for classpath file (--output-directory value) + /// - resolvedClasspath: Complete dependency classpath information + /// mutating func writeSwiftJavaClasspathFile( swiftModule: String, outputDirectory: String, @@ -218,6 +240,7 @@ extension SwiftJava.ResolveCommand { return camelCased } + // copy gradlew & gradle.bat from root, throws error if there is no gradle setup. func copyGradlew(to resolverWorkDirectory: URL) throws { var searchDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) From 91d21328c7d9776108e979a82f8faf786f44b54d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 15 Aug 2025 12:36:46 +0900 Subject: [PATCH 130/178] gh action: also run test jobs with nightly (#358) --- .github/actions/prepare_env/action.yml | 3 +++ .github/workflows/pull_request.yml | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 0e4a1ca3..3fdd5d4b 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -4,6 +4,9 @@ description: 'Prepare the CI environment by installing Swift and selected JDK et runs: using: composite steps: + - name: Check Swift version + shell: bash + run: swift -version - name: Set up JDK ${{ matrix.jdk_version }} uses: actions/setup-java@v4 with: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 89ca4289..b2d8ee7c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -115,7 +115,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] # no nightly checks for macOS + swift_version: ['6.1.2'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From e29deed3ad7bf0de7463509adb51b425f4ed510a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 15 Aug 2025 20:15:39 +0900 Subject: [PATCH 131/178] Allow config if to auto load native libraries with -Dswift-java.auto-load-libraries=false (#364) --- .../FFM/FFMSwift2JavaGenerator.swift | 13 ++++---- .../swift/swiftkit/core/SwiftLibraries.java | 31 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index c66b0029..9c9154a4 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -194,8 +194,8 @@ extension FFMSwift2JavaGenerator { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { - System.loadLibrary(SwiftLibraries.STDLIB_DYLIB_NAME); - System.loadLibrary("SwiftKitSwift"); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); System.loadLibrary(LIB_NAME); return true; } @@ -343,10 +343,11 @@ extension FFMSwift2JavaGenerator { """ static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup(); private static SymbolLookup getSymbolLookup() { - // Ensure Swift and our Lib are loaded during static initialization of the class. - SwiftLibraries.loadLibrary("swiftCore"); - SwiftLibraries.loadLibrary("SwiftKitSwift"); - SwiftLibraries.loadLibrary(LIB_NAME); + if (SwiftLibraries.AUTO_LOAD_LIBS) { + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(LIB_NAME); + } if (PlatformUtils.isMacOS()) { return SymbolLookup.libraryLookup(System.mapLibraryName(LIB_NAME), LIBRARY_ARENA) diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index 3230e52a..a093cc50 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -24,18 +24,28 @@ public final class SwiftLibraries { - public static final String STDLIB_DYLIB_NAME = "swiftCore"; - public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + // Library names of core Swift and SwiftKit + public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; + public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; + public static final String LIB_NAME_SWIFTKITSWIFT = "SwiftKitSwift"; - private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; + /** + * Allows for configuration if jextracted types should automatically attempt to load swiftCore and the library type is from. + *

+ * If all libraries you need to load are available in paths passed to {@code -Djava.library.path} this should work correctly, + * however if attempting to load libraries from e.g. the jar as a resource, you may want to disable this. + */ + public static final boolean AUTO_LOAD_LIBS = System.getProperty("swift-java.auto-load-libraries") == null ? + true + : Boolean.getBoolean("swiftkit.auto-load-libraries"); @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); public static boolean loadLibraries(boolean loadSwiftKit) { - System.loadLibrary(STDLIB_DYLIB_NAME); + System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); if (loadSwiftKit) { - System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); } return true; } @@ -43,17 +53,6 @@ public static boolean loadLibraries(boolean loadSwiftKit) { // ==== ------------------------------------------------------------------------------------------------------------ // Loading libraries - public static void loadLibrary(String libname) { - // TODO: avoid concurrent loadResource calls; one load is enough esp since we cause File IO when we do that - try { - // try to load a dylib from our classpath, e.g. when we included it in our jar - loadResourceLibrary(libname); - } catch (UnsatisfiedLinkError | RuntimeException e) { - // fallback to plain system path loading - System.loadLibrary(libname); - } - } - public static void loadResourceLibrary(String libname) { String resourceName = PlatformUtils.dynamicLibraryName(libname); if (CallTraces.TRACE_DOWNCALLS) { From 66f7a15b6ec915faf0fc3755649356fd935fc987 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Fri, 15 Aug 2025 14:29:19 +0200 Subject: [PATCH 132/178] Update .spi.yml (#367) --- .spi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.spi.yml b/.spi.yml index 58345f25..e7159a31 100644 --- a/.spi.yml +++ b/.spi.yml @@ -6,3 +6,5 @@ builder: JavaKit, SwiftKitSwift ] + # Drop this version pinning once 6.2 is released and docs are built with 6.2 by default to prevent it staying on 6.2 forever. + swift_version: 6.2 From f8de1460314bd68727fcb3e6a888acd0d4698ac4 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 16 Aug 2025 16:46:00 +0200 Subject: [PATCH 133/178] [JExtract/JNI] Import enums (#346) --- .../Sources/MySwiftLibrary/Alignment.swift | 18 + .../Sources/MySwiftLibrary/Vehicle.swift | 63 ++++ .../Sources/MySwiftLibrary/swift-java.config | 1 + .../java/com/example/swift/EnumBenchmark.java | 60 ++++ .../com/example/swift/AlignmentEnumTest.java | 41 +++ .../com/example/swift/VehicleEnumTest.java | 206 ++++++++++++ .../Convenience/String+Extensions.swift | 8 + .../Convenience/SwiftSyntax+Extensions.swift | 2 + ...Swift2JavaGenerator+FunctionLowering.swift | 4 + ...MSwift2JavaGenerator+JavaTranslation.swift | 2 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 53 +++ Sources/JExtractSwiftLib/JNI/JNICaching.swift | 31 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 116 ++++++- ...ISwift2JavaGenerator+JavaTranslation.swift | 302 ++++++++++++++--- ...wift2JavaGenerator+NativeTranslation.swift | 49 ++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 159 ++++++++- .../JNI/JNISwift2JavaGenerator.swift | 1 + .../JExtractSwiftLib/Swift2JavaVisitor.swift | 88 ++++- .../SwiftTypes/SwiftEnumCaseParameter.swift | 32 ++ .../SwiftTypes/SwiftFunctionSignature.swift | 28 +- .../SwiftNominalTypeDeclaration.swift | 8 + .../SwiftTypes/SwiftParameter.swift | 10 + .../SwiftTypes/SwiftType.swift | 13 + .../JavaKit/Helpers/_JNIMethodIDCache.swift | 59 ++++ Sources/JavaTypes/Mangling.swift | 7 +- .../Documentation.docc/SupportedFeatures.md | 113 ++++++- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 313 ++++++++++++++++++ .../JNI/JNIOptionalTests.swift | 34 +- 28 files changed, 1702 insertions(+), 119 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift create mode 100644 Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java create mode 100644 Sources/JExtractSwiftLib/JNI/JNICaching.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift create mode 100644 Sources/JavaKit/Helpers/_JNIMethodIDCache.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift new file mode 100644 index 00000000..760c564b --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift @@ -0,0 +1,18 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public enum Alignment: String { + case horizontal + case vertical +} diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift new file mode 100644 index 00000000..9d9155ad --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public enum Vehicle { + case bicycle + case car(String, trailer: String?) + case motorbike(String, horsePower: Int64, helmets: Int32?) + indirect case transformer(front: Vehicle, back: Vehicle) + case boat(passengers: Int32?, length: Int16?) + + public init?(name: String) { + switch name { + case "bicycle": self = .bicycle + case "car": self = .car("Unknown", trailer: nil) + case "motorbike": self = .motorbike("Unknown", horsePower: 0, helmets: nil) + case "boat": self = .boat(passengers: nil, length: nil) + default: return nil + } + } + + public var name: String { + switch self { + case .bicycle: "bicycle" + case .car: "car" + case .motorbike: "motorbike" + case .transformer: "transformer" + case .boat: "boat" + } + } + + public func isFasterThan(other: Vehicle) -> Bool { + switch (self, other) { + case (.bicycle, .bicycle), (.bicycle, .car), (.bicycle, .motorbike), (.bicycle, .transformer): false + case (.car, .bicycle): true + case (.car, .motorbike), (.car, .transformer), (.car, .car): false + case (.motorbike, .bicycle), (.motorbike, .car): true + case (.motorbike, .motorbike), (.motorbike, .transformer): false + case (.transformer, .bicycle), (.transformer, .car), (.transformer, .motorbike): true + case (.transformer, .transformer): false + default: false + } + } + + public mutating func upgrade() { + switch self { + case .bicycle: self = .car("Unknown", trailer: nil) + case .car: self = .motorbike("Unknown", horsePower: 0, helmets: nil) + case .motorbike: self = .transformer(front: .car("BMW", trailer: nil), back: self) + case .transformer, .boat: break + } + } +} diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index bb637f34..be44c2fd 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,4 +1,5 @@ { "javaPackage": "com.example.swift", "mode": "jni", + "logLevel": ["debug"] } diff --git a/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java b/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java new file mode 100644 index 00000000..d3de624b --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.swift.swiftkit.core.ClosableSwiftArena; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) +public class EnumBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + ClosableSwiftArena arena; + Vehicle vehicle; + + @Setup(Level.Trial) + public void beforeAll() { + arena = SwiftArena.ofConfined(); + vehicle = Vehicle.motorbike("Yamaha", 900, OptionalInt.empty(), arena); + } + + @TearDown(Level.Trial) + public void afterAll() { + arena.close(); + } + } + + @Benchmark + public Vehicle.Motorbike getAssociatedValues(BenchmarkState state, Blackhole bh) { + Vehicle.Motorbike motorbike = state.vehicle.getAsMotorbike().orElseThrow(); + bh.consume(motorbike.arg0()); + bh.consume(motorbike.horsePower()); + return motorbike; + } +} \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java new file mode 100644 index 00000000..6be85c75 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +public class AlignmentEnumTest { + @Test + void rawValue() { + try (var arena = SwiftArena.ofConfined()) { + Optional invalid = Alignment.init("invalid", arena); + assertFalse(invalid.isPresent()); + + Optional horizontal = Alignment.init("horizontal", arena); + assertTrue(horizontal.isPresent()); + assertEquals("horizontal", horizontal.get().getRawValue()); + + Optional vertical = Alignment.init("vertical", arena); + assertTrue(vertical.isPresent()); + assertEquals("vertical", vertical.get().getRawValue()); + } + } +} \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java new file mode 100644 index 00000000..c533603a --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java @@ -0,0 +1,206 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class VehicleEnumTest { + @Test + void bicycle() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + assertNotNull(vehicle); + } + } + + @Test + void car() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("Porsche 911", Optional.empty(), arena); + assertNotNull(vehicle); + } + } + + @Test + void motorbike() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.motorbike("Yamaha", 750, OptionalInt.empty(), arena); + assertNotNull(vehicle); + } + } + + @Test + void initName() { + try (var arena = SwiftArena.ofConfined()) { + assertFalse(Vehicle.init("bus", arena).isPresent()); + Optional vehicle = Vehicle.init("car", arena); + assertTrue(vehicle.isPresent()); + assertNotNull(vehicle.get()); + } + } + + @Test + void nameProperty() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + assertEquals("bicycle", vehicle.getName()); + } + } + + @Test + void isFasterThan() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle bicycle = Vehicle.bicycle(arena); + Vehicle car = Vehicle.car("Porsche 911", Optional.empty(), arena); + assertFalse(bicycle.isFasterThan(car)); + assertTrue(car.isFasterThan(bicycle)); + } + } + + @Test + void upgrade() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + assertEquals("bicycle", vehicle.getName()); + vehicle.upgrade(); + assertEquals("car", vehicle.getName()); + vehicle.upgrade(); + assertEquals("motorbike", vehicle.getName()); + } + } + + @Test + void getAsBicycle() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + Vehicle.Bicycle bicycle = vehicle.getAsBicycle().orElseThrow(); + assertNotNull(bicycle); + } + } + + @Test + void getAsCar() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); + Vehicle.Car car = vehicle.getAsCar().orElseThrow(); + assertEquals("BMW", car.arg0()); + + vehicle = Vehicle.car("BMW", Optional.of("Long trailer"), arena); + car = vehicle.getAsCar().orElseThrow(); + assertEquals("Long trailer", car.trailer().orElseThrow()); + } + } + + @Test + void getAsMotorbike() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.motorbike("Yamaha", 750, OptionalInt.empty(), arena); + Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); + assertEquals("Yamaha", motorbike.arg0()); + assertEquals(750, motorbike.horsePower()); + assertEquals(OptionalInt.empty(), motorbike.helmets()); + + vehicle = Vehicle.motorbike("Yamaha", 750, OptionalInt.of(2), arena); + motorbike = vehicle.getAsMotorbike().orElseThrow(); + assertEquals(OptionalInt.of(2), motorbike.helmets()); + } + } + + @Test + void getAsTransformer() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.transformer(Vehicle.bicycle(arena), Vehicle.car("BMW", Optional.empty(), arena), arena); + Vehicle.Transformer transformer = vehicle.getAsTransformer(arena).orElseThrow(); + assertTrue(transformer.front().getAsBicycle().isPresent()); + assertEquals("BMW", transformer.back().getAsCar().orElseThrow().arg0()); + } + } + + @Test + void getAsBoat() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.boat(OptionalInt.of(10), Optional.of((short) 1), arena); + Vehicle.Boat boat = vehicle.getAsBoat().orElseThrow(); + assertEquals(OptionalInt.of(10), boat.passengers()); + assertEquals(Optional.of((short) 1), boat.length()); + } + } + + @Test + void associatedValuesAreCopied() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); + Vehicle.Car car = vehicle.getAsCar().orElseThrow(); + assertEquals("BMW", car.arg0()); + vehicle.upgrade(); + Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); + assertNotNull(motorbike); + // Motorbike should still remain + assertEquals("BMW", car.arg0()); + } + } + + @Test + void getDiscriminator() { + try (var arena = SwiftArena.ofConfined()) { + assertEquals(Vehicle.Discriminator.BICYCLE, Vehicle.bicycle(arena).getDiscriminator()); + assertEquals(Vehicle.Discriminator.CAR, Vehicle.car("BMW", Optional.empty(), arena).getDiscriminator()); + assertEquals(Vehicle.Discriminator.MOTORBIKE, Vehicle.motorbike("Yamaha", 750, OptionalInt.empty(), arena).getDiscriminator()); + assertEquals(Vehicle.Discriminator.TRANSFORMER, Vehicle.transformer(Vehicle.bicycle(arena), Vehicle.bicycle(arena), arena).getDiscriminator()); + } + } + + @Test + void getCase() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + Vehicle.Case caseElement = vehicle.getCase(arena); + assertInstanceOf(Vehicle.Bicycle.class, caseElement); + } + } + + @Test + void switchGetCase() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); + switch (vehicle.getCase(arena)) { + case Vehicle.Bicycle b: + fail("Was bicycle"); + break; + case Vehicle.Car car: + assertEquals("BMW", car.arg0()); + break; + case Vehicle.Motorbike motorbike: + fail("Was motorbike"); + break; + case Vehicle.Transformer transformer: + fail("Was transformer"); + break; + case Vehicle.Boat b: + fail("Was boat"); + break; + } + } + } + +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 0f5cfeac..25c46366 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -24,6 +24,14 @@ extension String { return "\(f.uppercased())\(String(dropFirst()))" } + var firstCharacterLowercased: String { + guard let f = first else { + return self + } + + return "\(f.lowercased())\(String(dropFirst()))" + } + /// Returns whether the string is of the format `isX` var hasJavaBooleanNamingConvention: Bool { guard self.hasPrefix("is"), self.count > 2 else { diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index e71300af..3719d99d 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -253,6 +253,8 @@ extension DeclSyntaxProtocol { } )) .triviaSanitizedDescription + case .enumCaseDecl(let node): + node.triviaSanitizedDescription default: fatalError("unimplemented \(self.kind)") } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 085a6331..0a61708e 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -835,6 +835,10 @@ extension LoweredFunctionSignature { case .setter: assert(paramExprs.count == 1) resultExpr = "\(callee) = \(paramExprs[0])" + + case .enumCase: + // This should not be called, but let's fatalError. + fatalError("Enum cases are not supported with FFM.") } // Lower the result. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 06a4d171..57f947a8 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -146,7 +146,7 @@ extension FFMSwift2JavaGenerator { let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName - case .function, .initializer: decl.name + case .function, .initializer, .enumCase: decl.name } // Signature. diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 4c4d9c0f..5655af00 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -22,6 +22,7 @@ package enum SwiftAPIKind { case initializer case getter case setter + case enumCase } /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been @@ -32,6 +33,7 @@ package class ImportedNominalType: ImportedDecl { package var initializers: [ImportedFunc] = [] package var methods: [ImportedFunc] = [] package var variables: [ImportedFunc] = [] + package var cases: [ImportedEnumCase] = [] init(swiftNominal: SwiftNominalTypeDeclaration) { self.swiftNominal = swiftNominal @@ -46,6 +48,56 @@ package class ImportedNominalType: ImportedDecl { } } +public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible { + /// The case name + public var name: String + + /// The enum parameters + var parameters: [SwiftEnumCaseParameter] + + var swiftDecl: any DeclSyntaxProtocol + + var enumType: SwiftNominalType + + /// A function that represents the Swift static "initializer" for cases + var caseFunction: ImportedFunc + + init( + name: String, + parameters: [SwiftEnumCaseParameter], + swiftDecl: any DeclSyntaxProtocol, + enumType: SwiftNominalType, + caseFunction: ImportedFunc + ) { + self.name = name + self.parameters = parameters + self.swiftDecl = swiftDecl + self.enumType = enumType + self.caseFunction = caseFunction + } + + public var description: String { + """ + ImportedEnumCase { + name: \(name), + parameters: \(parameters), + swiftDecl: \(swiftDecl), + enumType: \(enumType), + caseFunction: \(caseFunction) + } + """ + } +} + +extension ImportedEnumCase: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + public static func == (lhs: ImportedEnumCase, rhs: ImportedEnumCase) -> Bool { + return lhs === rhs + } +} + 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 @@ -113,6 +165,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { let prefix = switch self.apiKind { case .getter: "getter:" case .setter: "setter:" + case .enumCase: "case:" case .function, .initializer: "" } diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift new file mode 100644 index 00000000..cdb13e3f --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNICaching { + static func cacheName(for type: ImportedNominalType) -> String { + cacheName(for: type.swiftNominal.name) + } + + static func cacheName(for type: SwiftNominalType) -> String { + cacheName(for: type.nominalTypeDecl.name) + } + + private static func cacheName(for name: String) -> String { + "_JNI_\(name)" + } + + static func cacheMemberName(for enumCase: ImportedEnumCase) -> String { + "\(enumCase.enumType.nominalTypeDecl.name.firstCharacterLowercased)\(enumCase.name.firstCharacterUppercased)Cache" + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 6d82175f..9351252e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -138,6 +138,11 @@ extension JNISwift2JavaGenerator { printer.println() + if decl.swiftNominal.kind == .enum { + printEnumHelpers(&printer, decl) + printer.println() + } + for initializer in decl.initializers { printFunctionDowncallMethods(&printer, initializer) printer.println() @@ -200,6 +205,85 @@ extension JNISwift2JavaGenerator { } } + private func printEnumHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printEnumDiscriminator(&printer, decl) + printer.println() + printEnumCaseInterface(&printer, decl) + printer.println() + printEnumStaticInitializers(&printer, decl) + printer.println() + printEnumCases(&printer, decl) + } + + private func printEnumDiscriminator(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printer.printBraceBlock("public enum Discriminator") { printer in + printer.print( + decl.cases.map { $0.name.uppercased() }.joined(separator: ",\n") + ) + } + + // TODO: Consider whether all of these "utility" functions can be printed using our existing printing logic. + printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in + printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress())];") + } + printer.print("private static native int $getDiscriminator(long self);") + } + + private func printEnumCaseInterface(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printer.print("public sealed interface Case {}") + printer.println() + + let requiresSwiftArena = decl.cases.compactMap { + self.translatedEnumCase(for: $0) + }.contains(where: \.requiresSwiftArena) + + printer.printBraceBlock("public Case getCase(\(requiresSwiftArena ? "SwiftArena swiftArena$" : ""))") { printer in + printer.print("Discriminator discriminator = this.getDiscriminator();") + printer.printBraceBlock("switch (discriminator)") { printer in + for enumCase in decl.cases { + guard let translatedCase = self.translatedEnumCase(for: enumCase) else { + continue + } + let arenaArgument = translatedCase.requiresSwiftArena ? "swiftArena$" : "" + printer.print("case \(enumCase.name.uppercased()): return this.getAs\(enumCase.name.firstCharacterUppercased)(\(arenaArgument)).orElseThrow();") + } + } + printer.print(#"throw new RuntimeException("Unknown discriminator value " + discriminator);"#) + } + } + + private func printEnumStaticInitializers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + for enumCase in decl.cases { + printFunctionDowncallMethods(&printer, enumCase.caseFunction) + } + } + + private func printEnumCases(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + for enumCase in decl.cases { + guard let translatedCase = self.translatedEnumCase(for: enumCase) else { + return + } + + let members = translatedCase.translatedValues.map { + $0.parameter.renderParameter() + } + + let caseName = enumCase.name.firstCharacterUppercased + + // Print record + printer.printBraceBlock("public record \(caseName)(\(members.joined(separator: ", "))) implements Case") { printer in + let nativeParameters = zip(translatedCase.translatedValues, translatedCase.parameterConversions).flatMap { value, conversion in + ["\(conversion.native.javaType) \(value.parameter.name)"] + } + + printer.print("record $NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") + } + + self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction) + printer.println() + } + } + private func printFunctionDowncallMethods( _ printer: inout CodePrinter, _ decl: ImportedFunc @@ -260,17 +344,23 @@ extension JNISwift2JavaGenerator { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } - let translatedSignature = translatedDecl.translatedFunctionSignature + printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl) + } + private func printJavaBindingWrapperMethod( + _ printer: inout CodePrinter, + _ translatedDecl: TranslatedFunctionDecl, + importedFunc: ImportedFunc? = nil + ) { var modifiers = ["public"] - - if decl.isStatic || decl.isInitializer || !decl.hasParent { + if translatedDecl.isStatic { modifiers.append("static") } + let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } - let throwsClause = decl.isThrowing ? " throws Exception" : "" + let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") if !annotationsStr.isEmpty { annotationsStr += "\n" } @@ -279,7 +369,9 @@ extension JNISwift2JavaGenerator { // Print default global arena variation if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { - printDeclDocumentation(&printer, decl) + if let importedFunc { + printDeclDocumentation(&printer, importedFunc) + } printer.printBraceBlock( "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" ) { printer in @@ -298,18 +390,19 @@ extension JNISwift2JavaGenerator { if translatedSignature.requiresSwiftArena { parameters.append("SwiftArena swiftArena$") } - printDeclDocumentation(&printer, decl) + if let importedFunc { + printDeclDocumentation(&printer, importedFunc) + } printer.printBraceBlock( "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" ) { printer in - printDowncall(&printer, decl) + printDowncall(&printer, translatedDecl) } - printNativeFunction(&printer, decl) + printNativeFunction(&printer, translatedDecl) } - private func printNativeFunction(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl + private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { let nativeSignature = translatedDecl.nativeFunctionSignature let resultType = nativeSignature.result.javaType var parameters = nativeSignature.parameters.flatMap(\.parameters) @@ -327,9 +420,8 @@ extension JNISwift2JavaGenerator { private func printDowncall( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ translatedDecl: TranslatedFunctionDecl ) { - let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. let translatedFunctionSignature = translatedDecl.translatedFunctionSignature // Regular parameters. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 64ae23b7..0e169ec6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -41,12 +41,121 @@ extension JNISwift2JavaGenerator { return translated } + func translatedEnumCase( + for decl: ImportedEnumCase + ) -> TranslatedEnumCase? { + if let cached = translatedEnumCases[decl] { + return cached + } + + let translated: TranslatedEnumCase? + do { + let translation = JavaTranslation( + config: config, + swiftModuleName: swiftModuleName, + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) + translated = try translation.translate(enumCase: decl) + } catch { + self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") + translated = nil + } + + translatedEnumCases[decl] = translated + return translated + } + struct JavaTranslation { let config: Configuration let swiftModuleName: String let javaPackage: String let javaClassLookupTable: JavaClassLookupTable + func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase { + let nativeTranslation = NativeJavaTranslation( + config: self.config, + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) + + let methodName = "" // TODO: Used for closures, replace with better name? + let parentName = "" // TODO: Used for closures, replace with better name? + + let translatedValues = try self.translateParameters( + enumCase.parameters.map { ($0.name, $0.type) }, + methodName: methodName, + parentName: parentName + ) + + let conversions = try enumCase.parameters.enumerated().map { idx, parameter in + let resultName = parameter.name ?? "arg\(idx)" + let result = SwiftResult(convention: .direct, type: parameter.type) + var translatedResult = try self.translate(swiftResult: result, resultName: resultName) + translatedResult.conversion = .replacingPlaceholder(translatedResult.conversion, placeholder: "$nativeParameters.\(resultName)") + let nativeResult = try nativeTranslation.translate(swiftResult: result, resultName: resultName) + return (translated: translatedResult, native: nativeResult) + } + + let caseName = enumCase.name.firstCharacterUppercased + let enumName = enumCase.enumType.nominalTypeDecl.name + let nativeParametersType = JavaType.class(package: nil, name: "\(caseName).$NativeParameters") + let getAsCaseName = "getAs\(caseName)" + // If the case has no parameters, we can skip the native call. + let constructRecordConversion = JavaNativeConversionStep.method(.constant("Optional"), function: "of", arguments: [ + .constructJavaClass( + .commaSeparated(conversions.map(\.translated.conversion)), + .class(package: nil,name: caseName) + ) + ]) + let getAsCaseFunction = TranslatedFunctionDecl( + name: getAsCaseName, + isStatic: false, + isThrowing: false, + nativeFunctionName: "$\(getAsCaseName)", + parentName: enumName, + functionTypes: [], + translatedFunctionSignature: TranslatedFunctionSignature( + selfParameter: TranslatedParameter( + parameter: JavaParameter(name: "self", type: .long), + conversion: .aggregate( + [ + .ifStatement(.constant("getDiscriminator() != Discriminator.\(caseName.uppercased())"), thenExp: .constant("return Optional.empty();")), + .valueMemoryAddress(.placeholder) + ] + ) + ), + parameters: [], + resultType: TranslatedResult( + javaType: .class(package: nil, name: "Optional<\(caseName)>"), + outParameters: conversions.flatMap(\.translated.outParameters), + conversion: enumCase.parameters.isEmpty ? constructRecordConversion : .aggregate(variable: ("$nativeParameters", nativeParametersType), [constructRecordConversion]) + ) + ), + nativeFunctionSignature: NativeFunctionSignature( + selfParameter: NativeParameter( + parameters: [JavaParameter(name: "self", type: .long)], + conversion: .extractSwiftValue(.placeholder, swiftType: .nominal(enumCase.enumType), allowNil: false) + ), + parameters: [], + result: NativeResult( + javaType: nativeParametersType, + conversion: .placeholder, + outParameters: conversions.flatMap(\.native.outParameters) + ) + ) + ) + + return TranslatedEnumCase( + name: enumCase.name.firstCharacterUppercased, + enumName: enumCase.enumType.nominalTypeDecl.name, + original: enumCase, + translatedValues: translatedValues, + parameterConversions: conversions, + getAsCaseFunction: getAsCaseFunction + ) + } + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let nativeTranslation = NativeJavaTranslation( config: self.config, @@ -61,7 +170,7 @@ extension JNISwift2JavaGenerator { let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName - case .function, .initializer: decl.name + case .function, .initializer, .enumCase: decl.name } // Swift -> Java @@ -98,6 +207,8 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, + isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, + isThrowing: decl.isThrowing, nativeFunctionName: "$\(javaName)", parentName: parentName, functionTypes: funcTypes, @@ -136,31 +247,47 @@ extension JNISwift2JavaGenerator { methodName: String, parentName: String ) throws -> TranslatedFunctionSignature { - let parameters = try functionSignature.parameters.enumerated().map { idx, param in - let parameterName = param.parameterName ?? "arg\(idx))" + let parameters = try translateParameters( + functionSignature.parameters.map { ($0.parameterName, $0.type )}, + methodName: methodName, + parentName: parentName + ) + + // 'self' + let selfParameter = try self.translateSelfParameter(functionSignature.selfParameter, methodName: methodName, parentName: parentName) + + let resultType = try translate(swiftResult: functionSignature.result) + + return TranslatedFunctionSignature( + selfParameter: selfParameter, + parameters: parameters, + resultType: resultType + ) + } + + func translateParameters( + _ parameters: [(name: String?, type: SwiftType)], + methodName: String, + parentName: String + ) throws -> [TranslatedParameter] { + try parameters.enumerated().map { idx, param in + let parameterName = param.name ?? "arg\(idx)" return try translateParameter(swiftType: param.type, parameterName: parameterName, methodName: methodName, parentName: parentName) } + } + func translateSelfParameter(_ selfParameter: SwiftSelfParameter?, methodName: String, parentName: String) throws -> TranslatedParameter? { // 'self' - let selfParameter: TranslatedParameter? - if case .instance(let swiftSelf) = functionSignature.selfParameter { - selfParameter = try self.translateParameter( + if case .instance(let swiftSelf) = selfParameter { + return try self.translateParameter( swiftType: swiftSelf.type, parameterName: swiftSelf.parameterName ?? "self", methodName: methodName, parentName: parentName ) } else { - selfParameter = nil + return nil } - - let resultType = try translate(swiftResult: functionSignature.result) - - return TranslatedFunctionSignature( - selfParameter: selfParameter, - parameters: parameters, - resultType: resultType - ) } func translateParameter( @@ -343,7 +470,7 @@ extension JNISwift2JavaGenerator { } } - func translate(swiftResult: SwiftResult) throws -> TranslatedResult { + func translate(swiftResult: SwiftResult, resultName: String = "result") throws -> TranslatedResult { let swiftType = swiftResult.type // If the result type should cause any annotations on the method, include them here. @@ -357,7 +484,7 @@ extension JNISwift2JavaGenerator { guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - return try translateOptionalResult(wrappedType: genericArgs[0]) + return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { @@ -390,7 +517,7 @@ extension JNISwift2JavaGenerator { return TranslatedResult(javaType: .void, outParameters: [], conversion: .placeholder) case .optional(let wrapped): - return try translateOptionalResult(wrappedType: wrapped) + return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -398,8 +525,11 @@ extension JNISwift2JavaGenerator { } func translateOptionalResult( - wrappedType swiftType: SwiftType + wrappedType swiftType: SwiftType, + resultName: String = "result" ) throws -> TranslatedResult { + let discriminatorName = "\(resultName)$_discriminator$" + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) switch swiftType { @@ -425,6 +555,7 @@ extension JNISwift2JavaGenerator { conversion: .combinedValueToOptional( .placeholder, nextIntergralTypeWithSpaceForByte.javaType, + resultName: resultName, valueType: javaType, valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes, optionalType: optionalClass @@ -437,13 +568,14 @@ extension JNISwift2JavaGenerator { javaType: .class(package: nil, name: returnType), annotations: parameterAnnotations, outParameters: [ - OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1)) ], conversion: .toOptionalFromIndirectReturn( - discriminatorName: "result_discriminator$", + discriminatorName: .combinedName(component: "discriminator$"), optionalClass: optionalClass, javaType: javaType, - toValue: .placeholder + toValue: .placeholder, + resultName: resultName ) ) } @@ -459,13 +591,14 @@ extension JNISwift2JavaGenerator { javaType: returnType, annotations: parameterAnnotations, outParameters: [ - OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1)) ], conversion: .toOptionalFromIndirectReturn( - discriminatorName: "result_discriminator$", + discriminatorName: .combinedName(component: "discriminator$"), optionalClass: "Optional", javaType: .long, - toValue: .wrapMemoryAddressUnsafe(.placeholder, .class(package: nil, name: nominalTypeName)) + toValue: .wrapMemoryAddressUnsafe(.placeholder, .class(package: nil, name: nominalTypeName)), + resultName: resultName ) ) @@ -475,10 +608,38 @@ extension JNISwift2JavaGenerator { } } + struct TranslatedEnumCase { + /// The corresponding Java case class (CamelCased) + let name: String + + /// The name of the translated enum + let enumName: String + + /// The oringinal enum case. + let original: ImportedEnumCase + + /// A list of the translated associated values + let translatedValues: [TranslatedParameter] + + /// A list of parameter conversions + let parameterConversions: [(translated: TranslatedResult, native: NativeResult)] + + let getAsCaseFunction: TranslatedFunctionDecl + + /// Returns whether the parameters require an arena + var requiresSwiftArena: Bool { + parameterConversions.contains(where: \.translated.conversion.requiresSwiftArena) + } + } + struct TranslatedFunctionDecl { /// Java function name let name: String + let isStatic: Bool + + let isThrowing: Bool + /// The name of the native function let nativeFunctionName: String @@ -532,7 +693,7 @@ extension JNISwift2JavaGenerator { let outParameters: [OutParameter] /// Represents how to convert the Java native result into a user-facing result. - let conversion: JavaNativeConversionStep + var conversion: JavaNativeConversionStep } struct OutParameter { @@ -573,6 +734,9 @@ extension JNISwift2JavaGenerator { case constant(String) + /// `input_component` + case combinedName(component: String) + // Convert the results of the inner steps to a comma separated list. indirect case commaSeparated([JavaNativeConversionStep]) @@ -582,6 +746,9 @@ extension JNISwift2JavaGenerator { /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + /// Call `new \(Type)(\(placeholder))` + indirect case constructJavaClass(JavaNativeConversionStep, JavaType) + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type indirect case wrapMemoryAddressUnsafe(JavaNativeConversionStep, JavaType) @@ -591,7 +758,7 @@ extension JNISwift2JavaGenerator { case isOptionalPresent - indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, valueType: JavaType, valueSizeInBytes: Int, optionalType: String) + indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, resultName: String, valueType: JavaType, valueSizeInBytes: Int, optionalType: String) indirect case ternary(JavaNativeConversionStep, thenExp: JavaNativeConversionStep, elseExp: JavaNativeConversionStep) @@ -600,18 +767,18 @@ extension JNISwift2JavaGenerator { indirect case subscriptOf(JavaNativeConversionStep, arguments: [JavaNativeConversionStep]) static func toOptionalFromIndirectReturn( - discriminatorName: String, + discriminatorName: JavaNativeConversionStep, optionalClass: String, javaType: JavaType, - toValue valueConversion: JavaNativeConversionStep + toValue valueConversion: JavaNativeConversionStep, + resultName: String ) -> JavaNativeConversionStep { .aggregate( - name: "result$", - type: javaType, + variable: (name: "\(resultName)$", type: javaType), [ .ternary( .equals( - .subscriptOf(.constant(discriminatorName), arguments: [.constant("0")]), + .subscriptOf(discriminatorName, arguments: [.constant("0")]), .constant("1") ), thenExp: .method(.constant(optionalClass), function: "of", arguments: [valueConversion]), @@ -622,7 +789,12 @@ extension JNISwift2JavaGenerator { } /// Perform multiple conversions using the same input. - case aggregate(name: String, type: JavaType, [JavaNativeConversionStep]) + case aggregate(variable: (name: String, type: JavaType)? = nil, [JavaNativeConversionStep]) + + indirect case ifStatement(JavaNativeConversionStep, thenExp: JavaNativeConversionStep, elseExp: JavaNativeConversionStep? = nil) + + /// Access a member of the value + indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -635,6 +807,9 @@ extension JNISwift2JavaGenerator { case .constant(let value): return value + case .combinedName(let component): + return "\(placeholder)_\(component)" + case .commaSeparated(let list): return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ") @@ -649,6 +824,10 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" + case .constructJavaClass(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "new \(javaType.className!)(\(inner))" + case .call(let inner, let function): let inner = inner.render(&printer, placeholder) return "\(function)(\(inner))" @@ -663,22 +842,22 @@ extension JNISwift2JavaGenerator { let argsStr = args.joined(separator: ", ") return "\(inner).\(methodName)(\(argsStr))" - case .combinedValueToOptional(let combined, let combinedType, let valueType, let valueSizeInBytes, let optionalType): + case .combinedValueToOptional(let combined, let combinedType, let resultName, let valueType, let valueSizeInBytes, let optionalType): let combined = combined.render(&printer, placeholder) printer.print( """ - \(combinedType) combined$ = \(combined); - byte discriminator$ = (byte) (combined$ & 0xFF); + \(combinedType) \(resultName)_combined$ = \(combined); + byte \(resultName)_discriminator$ = (byte) (\(resultName)_combined$ & 0xFF); """ ) if valueType == .boolean { - printer.print("boolean value$ = ((byte) (combined$ >> 8)) != 0;") + printer.print("boolean \(resultName)_value$ = ((byte) (\(resultName)_combined$ >> 8)) != 0;") } else { - printer.print("\(valueType) value$ = (\(valueType)) (combined$ >> \(valueSizeInBytes * 8));") + printer.print("\(valueType) \(resultName)_value$ = (\(valueType)) (\(resultName)_combined$ >> \(valueSizeInBytes * 8));") } - return "discriminator$ == 1 ? \(optionalType).of(value$) : \(optionalType).empty()" + return "\(resultName)_discriminator$ == 1 ? \(optionalType).of(\(resultName)_value$) : \(optionalType).empty()" case .ternary(let cond, let thenExp, let elseExp): let cond = cond.render(&printer, placeholder) @@ -696,25 +875,50 @@ extension JNISwift2JavaGenerator { let arguments = arguments.map { $0.render(&printer, placeholder) } return "\(inner)[\(arguments.joined(separator: ", "))]" - case .aggregate(let name, let type, let steps): + case .aggregate(let variable, let steps): precondition(!steps.isEmpty, "Aggregate must contain steps") - printer.print("\(type) \(name) = \(placeholder);") + let toExplode: String + if let variable { + printer.print("\(variable.type) \(variable.name) = \(placeholder);") + toExplode = variable.name + } else { + toExplode = placeholder + } let steps = steps.map { - $0.render(&printer, name) + $0.render(&printer, toExplode) } return steps.last! + + case .ifStatement(let cond, let thenExp, let elseExp): + let cond = cond.render(&printer, placeholder) + printer.printBraceBlock("if (\(cond))") { printer in + printer.print(thenExp.render(&printer, placeholder)) + } + if let elseExp { + printer.printBraceBlock("else") { printer in + printer.print(elseExp.render(&printer, placeholder)) + } + } + + return "" + + case .replacingPlaceholder(let inner, let placeholder): + return inner.render(&printer, placeholder) } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent: + case .placeholder, .constant, .isOptionalPresent, .combinedName: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true + case .constructJavaClass(let inner, _): + return inner.requiresSwiftArena + case .valueMemoryAddress(let inner): return inner.requiresSwiftArena @@ -724,7 +928,7 @@ extension JNISwift2JavaGenerator { case .method(let inner, _, let args): return inner.requiresSwiftArena || args.contains(where: \.requiresSwiftArena) - case .combinedValueToOptional(let inner, _, _, _, _): + case .combinedValueToOptional(let inner, _, _, _, _, _): return inner.requiresSwiftArena case .ternary(let cond, let thenExp, let elseExp): @@ -736,11 +940,17 @@ extension JNISwift2JavaGenerator { case .subscriptOf(let inner, _): return inner.requiresSwiftArena - case .aggregate(_, _, let steps): + case .aggregate(_, let steps): return steps.contains(where: \.requiresSwiftArena) + case .ifStatement(let cond, let thenExp, let elseExp): + return cond.requiresSwiftArena || thenExp.requiresSwiftArena || (elseExp?.requiresSwiftArena ?? false) + case .call(let inner, _): - return inner.requiresSwiftArena + return inner.requiresSwiftArena + + case .replacingPlaceholder(let inner, _): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index e7f7efbe..f2e9522b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -61,6 +61,23 @@ extension JNISwift2JavaGenerator { ) } + func translateParameters( + _ parameters: [SwiftParameter], + translatedParameters: [TranslatedParameter], + methodName: String, + parentName: String + ) throws -> [NativeParameter] { + try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in + let parameterName = translatedParameter.parameter.name + return try translate( + swiftParameter: swiftParameter, + parameterName: parameterName, + methodName: methodName, + parentName: parentName + ) + } + } + func translate( swiftParameter: SwiftParameter, parameterName: String, @@ -231,8 +248,11 @@ extension JNISwift2JavaGenerator { } func translateOptionalResult( - wrappedType swiftType: SwiftType + wrappedType swiftType: SwiftType, + resultName: String = "result" ) throws -> NativeResult { + let discriminatorName = "\(resultName)_discriminator$" + switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { @@ -249,6 +269,7 @@ extension JNISwift2JavaGenerator { conversion: .getJNIValue( .optionalRaisingWidenIntegerType( .placeholder, + resultName: resultName, valueType: javaType, combinedSwiftType: nextIntergralTypeWithSpaceForByte.swiftType, valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes @@ -258,7 +279,6 @@ extension JNISwift2JavaGenerator { ) } else { // Use indirect byte array to store discriminator - let discriminatorName = "result_discriminator$" return NativeResult( javaType: javaType, @@ -284,8 +304,6 @@ extension JNISwift2JavaGenerator { } // Assume JExtract imported class - let discriminatorName = "result_discriminator$" - return NativeResult( javaType: .long, conversion: .optionalRaisingIndirectReturn( @@ -368,7 +386,8 @@ extension JNISwift2JavaGenerator { } func translate( - swiftResult: SwiftResult + swiftResult: SwiftResult, + resultName: String = "result" ) throws -> NativeResult { switch swiftResult.type { case .nominal(let nominalType): @@ -378,7 +397,7 @@ extension JNISwift2JavaGenerator { guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - return try translateOptionalResult(wrappedType: genericArgs[0]) + return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { @@ -399,7 +418,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)), + conversion: .getJNIValue(.allocateSwiftValue(name: resultName, swiftType: swiftResult.type)), outParameters: [] ) @@ -411,7 +430,7 @@ extension JNISwift2JavaGenerator { ) case .optional(let wrapped): - return try translateOptionalResult(wrappedType: wrapped) + return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) @@ -449,6 +468,9 @@ extension JNISwift2JavaGenerator { case constant(String) + /// `input_component` + case combinedName(component: String) + /// `value.getJNIValue(in:)` indirect case getJNIValue(NativeSwiftConversionStep) @@ -480,7 +502,7 @@ extension JNISwift2JavaGenerator { indirect case optionalChain(NativeSwiftConversionStep) - indirect case optionalRaisingWidenIntegerType(NativeSwiftConversionStep, valueType: JavaType, combinedSwiftType: SwiftKnownTypeDeclKind, valueSizeInBytes: Int) + indirect case optionalRaisingWidenIntegerType(NativeSwiftConversionStep, resultName: String, valueType: JavaType, combinedSwiftType: SwiftKnownTypeDeclKind, valueSizeInBytes: Int) indirect case optionalRaisingIndirectReturn(NativeSwiftConversionStep, returnType: JavaType, discriminatorParameterName: String, placeholderValue: NativeSwiftConversionStep) @@ -503,6 +525,9 @@ extension JNISwift2JavaGenerator { case .constant(let value): return value + case .combinedName(let component): + return "\(placeholder)_\(component)" + case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) return "\(inner).getJNIValue(in: environment!)" @@ -606,18 +631,18 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(inner)?" - case .optionalRaisingWidenIntegerType(let inner, let valueType, let combinedSwiftType, let valueSizeInBytes): + case .optionalRaisingWidenIntegerType(let inner, let resultName, let valueType, let combinedSwiftType, let valueSizeInBytes): let inner = inner.render(&printer, placeholder) let value = valueType == .boolean ? "$0 ? 1 : 0" : "$0" let combinedSwiftTypeName = combinedSwiftType.moduleAndName.name printer.print( """ - let value$ = \(inner).map { + let \(resultName)_value$ = \(inner).map { \(combinedSwiftTypeName)(\(value)) << \(valueSizeInBytes * 8) | \(combinedSwiftTypeName)(1) } ?? 0 """ ) - return "value$" + return "\(resultName)_value$" case .optionalRaisingIndirectReturn(let inner, let returnType, let discriminatorParameterName, let placeholderValue): printer.print("let result$: \(returnType.jniTypeName)") diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index ca580e60..6b8c435a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -80,6 +80,15 @@ extension JNISwift2JavaGenerator { } } + private func printJNICache(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printer.printBraceBlock("enum \(JNICaching.cacheName(for: type))") { printer in + for enumCase in type.cases { + guard let translatedCase = translatedEnumCase(for: enumCase) else { continue } + printer.print("static let \(JNICaching.cacheMemberName(for: enumCase)) = \(renderEnumCaseCacheInit(translatedCase))") + } + } + } + private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { printHeader(&printer) @@ -97,11 +106,24 @@ extension JNISwift2JavaGenerator { private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { printHeader(&printer) + printJNICache(&printer, type) + printer.println() + for initializer in type.initializers { printSwiftFunctionThunk(&printer, initializer) printer.println() } + if type.swiftNominal.kind == .enum { + printEnumDiscriminator(&printer, type) + printer.println() + + for enumCase in type.cases { + printEnumCase(&printer, enumCase) + printer.println() + } + } + for method in type.methods { printSwiftFunctionThunk(&printer, method) printer.println() @@ -115,6 +137,92 @@ extension JNISwift2JavaGenerator { printDestroyFunctionThunk(&printer, type) } + private func printEnumDiscriminator(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) + printCDecl( + &printer, + javaMethodName: "$getDiscriminator", + parentName: type.swiftNominal.name, + parameters: [selfPointerParam], + resultType: .int + ) { printer in + let selfPointer = self.printSelfJLongToUnsafeMutablePointer( + &printer, + swiftParentName: type.swiftNominal.name, + selfPointerParam + ) + printer.printBraceBlock("switch (\(selfPointer).pointee)") { printer in + for (idx, enumCase) in type.cases.enumerated() { + printer.print("case .\(enumCase.name): return \(idx)") + } + } + } + } + + private func printEnumCase(_ printer: inout CodePrinter, _ enumCase: ImportedEnumCase) { + guard let translatedCase = self.translatedEnumCase(for: enumCase) else { + return + } + + // Print static case initializer + printSwiftFunctionThunk(&printer, enumCase.caseFunction) + printer.println() + + // Print getAsCase method + if !translatedCase.translatedValues.isEmpty { + printEnumGetAsCaseThunk(&printer, translatedCase) + } + } + + private func renderEnumCaseCacheInit(_ enumCase: TranslatedEnumCase) -> String { + let nativeParametersClassName = "\(javaPackagePath)/\(enumCase.enumName)$\(enumCase.name)$$NativeParameters" + let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) + let methods = #"[.init(name: "", signature: "\#(methodSignature.mangledName)")]"# + + return #"_JNIMethodIDCache(environment: try! JavaVirtualMachine.shared().environment(), className: "\#(nativeParametersClassName)", methods: \#(methods))"# + } + + private func printEnumGetAsCaseThunk( + _ printer: inout CodePrinter, + _ enumCase: TranslatedEnumCase + ) { + printCDecl( + &printer, + enumCase.getAsCaseFunction + ) { printer in + let selfPointer = enumCase.getAsCaseFunction.nativeFunctionSignature.selfParameter!.conversion.render(&printer, "self") + let caseNames = enumCase.original.parameters.enumerated().map { idx, parameter in + parameter.name ?? "_\(idx)" + } + let caseNamesWithLet = caseNames.map { "let \($0)" } + let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) + printer.print( + """ + guard case .\(enumCase.original.name)(\(caseNamesWithLet.joined(separator: ", "))) = \(selfPointer).pointee else { + fatalError("Expected enum case '\(enumCase.original.name)', but was '\\(\(selfPointer).pointee)'!") + } + let cache$ = \(JNICaching.cacheName(for: enumCase.original.enumType)).\(JNICaching.cacheMemberName(for: enumCase.original)) + let class$ = cache$.javaClass + let method$ = _JNIMethodIDCache.Method(name: "", signature: "\(methodSignature.mangledName)") + let constructorID$ = cache$[method$] + """ + ) + let upcallArguments = zip(enumCase.parameterConversions, caseNames).map { conversion, caseName in + // '0' is treated the same as a null pointer. + let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? 0" : "" + let result = conversion.native.conversion.render(&printer, caseName) + return "\(result)\(nullConversion)" + } + printer.print( + """ + return withVaList([\(upcallArguments.joined(separator: ", "))]) { + return environment.interface.NewObjectV(environment, class$, constructorID$, $0) + } + """ + ) + } + } + private func printSwiftFunctionThunk( _ printer: inout CodePrinter, _ decl: ImportedFunc @@ -124,21 +232,9 @@ extension JNISwift2JavaGenerator { return } - let nativeSignature = translatedDecl.nativeFunctionSignature - var parameters = nativeSignature.parameters.flatMap(\.parameters) - - if let selfParameter = nativeSignature.selfParameter { - parameters += selfParameter.parameters - } - - parameters += nativeSignature.result.outParameters - printCDecl( &printer, - javaMethodName: translatedDecl.nativeFunctionName, - parentName: translatedDecl.parentName, - parameters: parameters, - resultType: nativeSignature.result.javaType + translatedDecl ) { printer in self.printFunctionDowncall(&printer, decl) } @@ -190,6 +286,18 @@ extension JNISwift2JavaGenerator { .joined(separator: ", ") result = "\(tryClause)\(callee).\(decl.name)(\(downcallArguments))" + case .enumCase: + let downcallArguments = zip( + decl.functionSignature.parameters, + arguments + ).map { originalParam, argument in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(argument)" + } + + let associatedValues = !downcallArguments.isEmpty ? "(\(downcallArguments.joined(separator: ", ")))" : "" + result = "\(callee).\(decl.name)\(associatedValues)" + case .getter: result = "\(tryClause)\(callee).\(decl.name)" @@ -228,6 +336,31 @@ extension JNISwift2JavaGenerator { } } + private func printCDecl( + _ printer: inout CodePrinter, + _ translatedDecl: TranslatedFunctionDecl, + _ body: (inout CodePrinter) -> Void + ) { + let nativeSignature = translatedDecl.nativeFunctionSignature + var parameters = nativeSignature.parameters.flatMap(\.parameters) + + if let selfParameter = nativeSignature.selfParameter { + parameters += selfParameter.parameters + } + + parameters += nativeSignature.result.outParameters + + printCDecl( + &printer, + javaMethodName: translatedDecl.nativeFunctionName, + parentName: translatedDecl.parentName, + parameters: parameters, + resultType: nativeSignature.result.javaType + ) { printer in + body(&printer) + } + } + private func printCDecl( _ printer: inout CodePrinter, javaMethodName: String, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index c5f3a5b6..60ec1b8b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -39,6 +39,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { /// Cached Java translation result. 'nil' indicates failed translation. var translatedDecls: [ImportedFunc: TranslatedFunctionDecl] = [:] + var translatedEnumCases: [ImportedEnumCase: TranslatedEnumCase] = [:] /// 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. diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index a933ff3e..bc6ac032 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -46,7 +46,7 @@ final class Swift2JavaVisitor { case .structDecl(let node): self.visit(nominalDecl: node, in: parent) case .enumDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(enumDecl: node, in: parent) case .protocolDecl(let node): self.visit(nominalDecl: node, in: parent) case .extensionDecl(let node): @@ -65,6 +65,8 @@ final class Swift2JavaVisitor { case .subscriptDecl: // TODO: Implement break + case .enumCaseDecl(let node): + self.visit(enumCaseDecl: node, in: parent) default: break @@ -83,6 +85,12 @@ final class Swift2JavaVisitor { } } + func visit(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { + self.visit(nominalDecl: node, in: parent) + + self.synthesizeRawRepresentableConformance(enumDecl: node, in: parent) + } + func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore @@ -131,6 +139,49 @@ final class Swift2JavaVisitor { } } + func visit(enumCaseDecl node: EnumCaseDeclSyntax, in typeContext: ImportedNominalType?) { + guard let typeContext else { + self.log.info("Enum case must be within a current type; \(node)") + return + } + + do { + for caseElement in node.elements { + self.log.debug("Import case \(caseElement.name) of enum \(node.qualifiedNameForDebug)") + + let parameters = try caseElement.parameterClause?.parameters.map { + try SwiftEnumCaseParameter($0, lookupContext: translator.lookupContext) + } + + let signature = try SwiftFunctionSignature( + caseElement, + enclosingType: typeContext.swiftType, + lookupContext: translator.lookupContext + ) + + let caseFunction = ImportedFunc( + module: translator.swiftModuleName, + swiftDecl: node, + name: caseElement.name.text, + apiKind: .enumCase, + functionSignature: signature + ) + + let importedCase = ImportedEnumCase( + name: caseElement.name.text, + parameters: parameters ?? [], + swiftDecl: node, + enumType: SwiftNominalType(nominalTypeDecl: typeContext.swiftNominal), + caseFunction: caseFunction + ) + + typeContext.cases.append(importedCase) + } + } catch { + self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") + } + } + func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { guard node.shouldExtract(config: config, log: log) else { return @@ -213,6 +264,32 @@ final class Swift2JavaVisitor { typeContext.initializers.append(imported) } + + private func synthesizeRawRepresentableConformance(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { + guard let imported = translator.importedNominalType(node, parent: parent) else { + return + } + + if let firstInheritanceType = imported.swiftNominal.firstInheritanceType, + let inheritanceType = try? SwiftType( + firstInheritanceType, + lookupContext: translator.lookupContext + ), + inheritanceType.isRawTypeCompatible + { + if !imported.variables.contains(where: { $0.name == "rawValue" && $0.functionSignature.result.type != inheritanceType }) { + let decl: DeclSyntax = "public var rawValue: \(raw: inheritanceType.description) { get }" + self.visit(decl: decl, in: imported) + } + + imported.variables.first?.signatureString + + if !imported.initializers.contains(where: { $0.functionSignature.parameters.count == 1 && $0.functionSignature.parameters.first?.parameterName == "rawValue" && $0.functionSignature.parameters.first?.type == inheritanceType }) { + let decl: DeclSyntax = "public init?(rawValue: \(raw: inheritanceType))" + self.visit(decl: decl, in: imported) + } + } + } } extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { @@ -233,15 +310,6 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn return false } - if let node = self.as(InitializerDeclSyntax.self) { - let isFailable = node.optionalMark != nil - - if isFailable { - log.warning("Skip import '\(self.qualifiedNameForDebug)': failable initializer") - return false - } - } - return true } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift new file mode 100644 index 00000000..55682152 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftEnumCaseParameter: Equatable { + var name: String? + var type: SwiftType +} + +extension SwiftEnumCaseParameter { + init( + _ node: EnumCaseParameterSyntax, + lookupContext: SwiftTypeLookupContext + ) throws { + self.init( + name: node.firstName?.identifier?.name, + type: try SwiftType(node.type, lookupContext: lookupContext) + ) + } +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 85b96110..a5b01bee 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -71,11 +71,6 @@ extension SwiftFunctionSignature { throw SwiftFunctionTranslationError.missingEnclosingType(node) } - // We do not yet support failable initializers. - if node.optionalMark != nil { - throw SwiftFunctionTranslationError.failableInitializer(node) - } - let (genericParams, genericRequirements) = try Self.translateGenericParameters( parameterClause: node.genericParameterClause, whereClause: node.genericWhereClause, @@ -86,16 +81,37 @@ extension SwiftFunctionSignature { lookupContext: lookupContext ) + let type = node.optionalMark != nil ? .optional(enclosingType) : enclosingType + self.init( selfParameter: .initializer(enclosingType), parameters: parameters, - result: SwiftResult(convention: .direct, type: enclosingType), + result: SwiftResult(convention: .direct, type: type), effectSpecifiers: effectSpecifiers, genericParameters: genericParams, genericRequirements: genericRequirements ) } + init( + _ node: EnumCaseElementSyntax, + enclosingType: SwiftType, + lookupContext: SwiftTypeLookupContext + ) throws { + let parameters = try node.parameterClause?.parameters.map { param in + try SwiftParameter(param, lookupContext: lookupContext) + } + + self.init( + selfParameter: .initializer(enclosingType), + parameters: parameters ?? [], + result: SwiftResult(convention: .direct, type: enclosingType), + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] + ) + } + init( _ node: FunctionDeclSyntax, enclosingType: SwiftType?, diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index cef4e731..335979a4 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -85,6 +85,14 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { super.init(moduleName: moduleName, name: node.name.text) } + lazy var firstInheritanceType: TypeSyntax? = { + guard let firstInheritanceType = self.syntax?.inheritanceClause?.inheritedTypes.first else { + return nil + } + + return firstInheritanceType.type + }() + /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". lazy var isSendable: Bool = { // Check if Sendable is in the inheritance list diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift index 75d165e9..63f7d75b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift @@ -57,6 +57,16 @@ enum SwiftParameterConvention: Equatable { case `inout` } +extension SwiftParameter { + init(_ node: EnumCaseParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { + self.convention = .byValue + self.type = try SwiftType(node.type, lookupContext: lookupContext) + self.argumentLabel = nil + self.parameterName = node.firstName?.identifier?.name + self.argumentLabel = node.firstName?.identifier?.name + } +} + extension SwiftParameter { init(_ node: FunctionParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { // Determine the convention. The default is by-value, but there are diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 3cc14406..58bb65c3 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -103,6 +103,19 @@ enum SwiftType: Equatable { default: false } } + + var isRawTypeCompatible: Bool { + switch self { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string: + true + default: + false + } + default: false + } + } } extension SwiftType: CustomStringConvertible { diff --git a/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift b/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift new file mode 100644 index 00000000..a67d225f --- /dev/null +++ b/Sources/JavaKit/Helpers/_JNIMethodIDCache.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 +// +//===----------------------------------------------------------------------===// + +/// A cache used to hold references for JNI method and classes. +/// +/// This type is used internally in by the outputted JExtract wrappers +/// to improve performance of any JNI lookups. +public final class _JNIMethodIDCache: Sendable { + public struct Method: Hashable { + public let name: String + public let signature: String + + public init(name: String, signature: String) { + self.name = name + self.signature = signature + } + } + + nonisolated(unsafe) let _class: jclass? + nonisolated(unsafe) let methods: [Method: jmethodID] + + public var javaClass: jclass { + self._class! + } + + public init(environment: UnsafeMutablePointer!, className: String, methods: [Method]) { + guard let clazz = environment.interface.FindClass(environment, className) else { + fatalError("Class \(className) could not be found!") + } + self._class = environment.interface.NewGlobalRef(environment, clazz)! + self.methods = methods.reduce(into: [:]) { (result, method) in + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } + } + } + + + public subscript(_ method: Method) -> jmethodID? { + methods[method] + } + + public func cleanup(environment: UnsafeMutablePointer!) { + environment.interface.DeleteGlobalRef(environment, self._class) + } +} diff --git a/Sources/JavaTypes/Mangling.swift b/Sources/JavaTypes/Mangling.swift index 74e2e0b8..f0dbd484 100644 --- a/Sources/JavaTypes/Mangling.swift +++ b/Sources/JavaTypes/Mangling.swift @@ -36,7 +36,7 @@ extension JavaType { case .void: "V" case .array(let elementType): "[" + elementType.mangledName case .class(package: let package, name: let name): - "L\(package!).\(name);".replacingPeriodsWithSlashes() + "L\(package!).\(name.replacingPeriodsWithDollars());".replacingPeriodsWithSlashes() } } } @@ -145,4 +145,9 @@ extension StringProtocol { fileprivate func replacingSlashesWithPeriods() -> String { return String(self.map { $0 == "/" ? "." as Character : $0 }) } + + /// Return the string after replacing all of the periods (".") with slashes ("$"). + fileprivate func replacingPeriodsWithDollars() -> String { + return String(self.map { $0 == "." ? "$" as Character : $0 }) + } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 130c333b..c5de6c45 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -49,7 +49,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Initializers: `class`, `struct` | ✅ | ✅ | | Optional Initializers / Throwing Initializers | ❌ | ❌ | | Deinitializers: `class`, `struct` | ✅ | ✅ | -| `enum`, `actor` | ❌ | ❌ | +| `enum` | ❌ | ✅ | +| `actor` | ❌ | ❌ | | Global Swift `func` | ✅ | ✅ | | Class/struct member `func` | ✅ | ✅ | | Throwing functions: `func x() throws` | ❌ | ✅ | @@ -157,3 +158,113 @@ you are expected to add a Guava dependency to your Java project. | `Double` | `double` | > Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. + +### Enums + +> Note: Enums are currently only supported in JNI mode. + +Swift enums are extracted into a corresponding Java `class`. To support associated values +all cases are also extracted as Java `record`s. + +Consider the following Swift enum: +```swift +public enum Vehicle { + case car(String) + case bicycle(maker: String) +} +``` +You can then instantiate a case of `Vehicle` by using one of the static methods: +```java +try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", arena); + Optional car = vehicle.getAsCar(); + assertEquals("BMW", car.orElseThrow().arg0()); +} +``` +As you can see above, to access the associated values of a case you can call one of the +`getAsX` methods that will return an Optional record with the associated values. +```java +try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bycicle("My Brand", arena); + Optional car = vehicle.getAsCar(); + assertFalse(car.isPresent()); + + Optional bicycle = vehicle.getAsBicycle(); + assertEquals("My Brand", bicycle.orElseThrow().maker()); +} +``` + +#### Switching and pattern matching + +If you only need to switch on the case and not access any associated values, +you can use the `getDiscriminator()` method: +```java +Vehicle vehicle = ...; +switch (vehicle.getDiscriminator()) { + case BICYCLE: + System.out.println("I am a bicycle!"); + break + case CAR: + System.out.println("I am a car!"); + break +} +``` +If you also want access to the associated values, you have various options +depending on the Java version you are using. +If you are running Java 21+ you can use [pattern matching for switch](https://openjdk.org/jeps/441): +```java +Vehicle vehicle = ...; +switch (vehicle.getCase()) { + case Vehicle.Bicycle b: + System.out.println("Bicycle maker: " + b.maker()); + break + case Vehicle.Car c: + System.out.println("Car: " + c.arg0()); + break +} +``` +For Java 16+ you can use [pattern matching for instanceof](https://openjdk.org/jeps/394) +```java +Vehicle vehicle = ...; +Vehicle.Case case = vehicle.getCase(); +if (case instanceof Vehicle.Bicycle b) { + System.out.println("Bicycle maker: " + b.maker()); +} else if(case instanceof Vehicle.Car c) { + System.out.println("Car: " + c.arg0()); +} +``` +For any previous Java versions you can resort to casting the `Case` to the expected type: +```java +Vehicle vehicle = ...; +Vehicle.Case case = vehicle.getCase(); +if (case instanceof Vehicle.Bicycle) { + Vehicle.Bicycle b = (Vehicle.Bicycle) case; + System.out.println("Bicycle maker: " + b.maker()); +} else if(case instanceof Vehicle.Car) { + Vehicle.Car c = (Vehicle.Car) case; + System.out.println("Car: " + c.arg0()); +} +``` + +#### RawRepresentable enums + +JExtract also supports extracting enums that conform to `RawRepresentable` +by giving access to an optional initializer and the `rawValue` variable. +Consider the following example: +```swift +public enum Alignment: String { + case horizontal + case vertical +} +``` +you can then initialize `Alignment` from a `String` and also retrieve back its `rawValue`: +```java +try (var arena = SwiftArena.ofConfined()) { + Optional alignment = Alignment.init("horizontal", arena); + assertEqual(HORIZONTAL, alignment.orElseThrow().getDiscriminator()); + assertEqual("horizontal", alignment.orElseThrow().getRawValue()); +} +``` + + + diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift new file mode 100644 index 00000000..47765043 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -0,0 +1,313 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIEnumTests { + let source = """ + public enum MyEnum { + case first + case second(String) + case third(x: Int64, y: Int32) + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """, + """ + public final class MyEnum extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + """, + """ + private MyEnum(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + """ + public static MyEnum wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new MyEnum(selfPointer, swiftArena); + } + """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyEnum.$createDestroyFunction", + "this", this, + "self", self$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyEnum.$destroy", "self", self$); + } + MyEnum.$destroy(self$); + } + }; + """ + ]) + } + + @Test + func generatesDiscriminator_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public enum Discriminator { + FIRST, + SECOND, + THIRD + } + """, + """ + public Discriminator getDiscriminator() { + return Discriminator.values()[$getDiscriminator(this.$memoryAddress())]; + } + """, + """ + private static native int $getDiscriminator(long self); + """ + ]) + } + + @Test + func generatesDiscriminator_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyEnum__00024getDiscriminator__J") + func Java_com_example_swift_MyEnum__00024getDiscriminator__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jint { + ... + switch (self$.pointee) { + case .first: return 0 + case .second: return 1 + case .third: return 2 + } + } + """ + ]) + } + + @Test + func generatesCases_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public sealed interface Case {} + """, + """ + public Case getCase() { + Discriminator discriminator = this.getDiscriminator(); + switch (discriminator) { + case FIRST: return this.getAsFirst().orElseThrow(); + case SECOND: return this.getAsSecond().orElseThrow(); + case THIRD: return this.getAsThird().orElseThrow(); + } + throw new RuntimeException("Unknown discriminator value " + discriminator); + } + """, + """ + public record First() implements Case { + record $NativeParameters() {} + } + """, + """ + public record Second(java.lang.String arg0) implements Case { + record $NativeParameters(java.lang.String arg0) {} + } + """, + """ + public record Third(long x, int y) implements Case { + record $NativeParameters(long x, int y) {} + } + """ + ]) + } + + @Test + func generatesCaseInitializers_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static MyEnum first(SwiftArena swiftArena$) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$first(), swiftArena$); + } + """, + """ + public static MyEnum second(java.lang.String arg0, SwiftArena swiftArena$) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$second(arg0), swiftArena$); + } + """, + """ + public static MyEnum third(long x, int y, SwiftArena swiftArena$) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$third(x, y), swiftArena$); + } + """ + ]) + } + + @Test + func generatesCaseInitializers_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyEnum__00024first__") + func Java_com_example_swift_MyEnum__00024first__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyEnum.first) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """, + """ + @_cdecl("Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2") + func Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg0: jstring?) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """, + """ + @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") + func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment!), y: Int32(fromJNI: y, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """ + ]) + } + + @Test + func generatesGetAsCase_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public Optional getAsFirst() { + if (getDiscriminator() != Discriminator.FIRST) { + return Optional.empty(); + } + return Optional.of(new First()); + } + """, + """ + public Optional getAsSecond() { + if (getDiscriminator() != Discriminator.SECOND) { + return Optional.empty(); + } + Second.$NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); + return Optional.of(new Second($nativeParameters.arg0)); + } + """, + """ + public Optional getAsThird() { + if (getDiscriminator() != Discriminator.THIRD) { + return Optional.empty(); + } + Third.$NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); + return Optional.of(new Third($nativeParameters.x, $nativeParameters.y)); + } + """ + ]) + } + + @Test + func generatesGetAsCase_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyEnum__00024getAsSecond__J") + func Java_com_example_swift_MyEnum__00024getAsSecond__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jobject? { + ... + guard case .second(let _0) = self$.pointee else { + fatalError("Expected enum case 'second', but was '\\(self$.pointee)'!") + } + let cache$ = _JNI_MyEnum.myEnumSecondCache + let class$ = cache$.javaClass + let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") + let constructorID$ = cache$[method$] + return withVaList([_0.getJNIValue(in: environment!) ?? 0]) { + return environment.interface.NewObjectV(environment, class$, constructorID$, $0) + } + } + """, + """ + @_cdecl("Java_com_example_swift_MyEnum__00024getAsThird__J") + func Java_com_example_swift_MyEnum__00024getAsThird__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jobject? { + ... + guard case .third(let x, let y) = self$.pointee else { + fatalError("Expected enum case 'third', but was '\\(self$.pointee)'!") + } + let cache$ = _JNI_MyEnum.myEnumThirdCache + let class$ = cache$.javaClass + let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") + let constructorID$ = cache$[method$] + return withVaList([x.getJNIValue(in: environment!), y.getJNIValue(in: environment!)]) { + return environment.interface.NewObjectV(environment, class$, constructorID$, $0) + } + } + """ + ]) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index 2186e227..548a2eac 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -47,10 +47,10 @@ struct JNIOptionalTests { * } */ public static OptionalInt optionalSugar(OptionalLong arg) { - long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); - byte discriminator$ = (byte) (combined$ & 0xFF); - int value$ = (int) (combined$ >> 32); - return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); + long result_combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); + byte result_discriminator$ = (byte) (result_combined$ & 0xFF); + int result_value$ = (int) (result_combined$ >> 32); + return result_discriminator$ == 1 ? OptionalInt.of(result_value$) : OptionalInt.empty(); } """, """ @@ -72,10 +72,10 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { - let value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { Int64($0) << 32 | Int64(1) } ?? 0 - return value$.getJNIValue(in: environment!) + return result_value$.getJNIValue(in: environment!) } """ ] @@ -98,9 +98,9 @@ struct JNIOptionalTests { * } */ public static Optional optionalExplicit(Optional arg) { - byte[] result_discriminator$ = new byte[1]; - java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); + byte[] result$_discriminator$ = new byte[1]; + java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result$_discriminator$); + return (result$_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); } """, """ @@ -127,12 +127,12 @@ struct JNIOptionalTests { result$ = innerResult$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 else { result$ = String.jniPlaceholderValue var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 return result$ } """ @@ -156,9 +156,9 @@ struct JNIOptionalTests { * } */ public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { - byte[] result_discriminator$ = new byte[1]; - long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); + byte[] result$_discriminator$ = new byte[1]; + long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result$_discriminator$); + return (result$_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); } """, """ @@ -190,12 +190,12 @@ struct JNIOptionalTests { result$ = _resultBits$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 else { result$ = 0 var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 return result$ } """ @@ -243,7 +243,7 @@ struct JNIOptionalTests { func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { return JavaLong(javaThis: $0, environment: environment!) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:666 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:691 ) } """ From 612f04e48c430d4e38df21c51e95d52de31c08d3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 20 Aug 2025 16:22:52 +0900 Subject: [PATCH 134/178] Disable SPI docs for jni dependent targets (#369) docc plugin can't handle targets which need additional libraries, so we're blocked on generating docs for them in SPI until https://github.com/swiftlang/swift-docc-plugin/issues/112 is resolved --- .spi.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.spi.yml b/.spi.yml index e7159a31..f4074e0e 100644 --- a/.spi.yml +++ b/.spi.yml @@ -2,9 +2,7 @@ version: 1 builder: configs: - documentation_targets: [ - SwiftJavaDocumentation, - JavaKit, - SwiftKitSwift + SwiftJavaDocumentation ] # Drop this version pinning once 6.2 is released and docs are built with 6.2 by default to prevent it staying on 6.2 forever. swift_version: 6.2 From 1d2014849d325ea4efc6141114d54ee36ce3fcd1 Mon Sep 17 00:00:00 2001 From: David Ko Date: Mon, 25 Aug 2025 04:11:55 -0400 Subject: [PATCH 135/178] Update on SwiftJavaDocumentation (#372) Co-authored-by: Konrad `ktoso` Malawski --- .../Documentation.docc/SupportedFeatures.md | 4 ++-- .../Documentation.docc/index.md | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index c5de6c45..8d193149 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -33,10 +33,10 @@ Java `native` functions. JavaKit simplifies the type conversions | Java `abstract class` | TODO | | Java `enum` | ❌ | | Java methods: `static`, member | ✅ `@JavaMethod` | -| **This list is very work in progress** | | +| **This list is very work in progress** | | -### JExtract: Java -> Swift +### JExtract – calling Swift from Java SwiftJava's `swift-java jextract` tool automates generating Java bindings from Swift sources. diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md index 7c695a71..460f396d 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md @@ -11,9 +11,21 @@ Please refer to articles about the specific direction of interoperability you ar ### Getting started -TODO: Some general intro +**SwiftJava** provides a set of tools and libraries to enable Java and Swift interoperability. It allows developers to generate bindings to either language from the other, by using either source generation (for Java consuming Swift code) or a combination of Swift macros and source generation (for Swift consuming Java libraries). -If you prefer a video introduction, you may want to this +The generated code is highly efficient and less error-prone than manually mapping, and also guarantees memory safety across the boundaries between the languages. + +Reasons why you might want to reach for Swift and Java interoperability include, but are not limited to, the following scenarios: +- Incremental adoption of Swift in an existing Java codebase +- Reuse existing libraries which exist in one ecosystem, but don't have a direct equivalent in the other + +SwiftJava is offering several core libraries which support language interoperability: +- `JavaKit` (Swift -> Java) - JNI-based support library and Swift macros +- `SwiftKit` (Java -> Swift) - Support library for Java calling Swift code (either using JNI or FFM) +- `swift-java` - command line tool; Supports source generation and also dependency management operations +- Build tool integration - SwiftPM Plugin + +If you prefer a video introduction, you may want to watch this [Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA) WWDC 2025 session, which is a quick overview of all the features and approaches offered by SwiftJava. @@ -25,7 +37,7 @@ which is a quick overview of all the features and approaches offered by SwiftJav - -### Source generation +### Source Generation - - From caa97a4e841ef9859fe93b8c754422e1cb73bd7a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 26 Aug 2025 07:26:47 +0900 Subject: [PATCH 136/178] Reorganize and rename modules; Centralize around SwiftJava prefix and naming (#366) --- .github/workflows/pull_request.yml | 8 +- .../JavaApiCallBenchmarks.swift | 4 +- Benchmarks/Package.swift | 6 +- Package.swift | 137 ++++++++++-------- .../JExtractSwiftPlugin.swift | 2 +- .../JavaCompilerPlugin.swift | 2 +- .../PluginsShared/JavaKitConfigurationShared | 1 - .../SwiftJavaConfigurationShared | 1 + Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 2 +- README.md | 2 +- Samples/JavaDependencySampleApp/Package.swift | 16 +- .../Sources/JavaDependencySample/main.swift | 8 +- Samples/JavaKitSampleApp/Package.swift | 6 +- .../JavaKitExample/JavaKitExample.swift | 4 +- Samples/JavaProbablyPrime/Package.swift | 4 +- .../Sources/JavaProbablyPrime/prime.swift | 2 +- Samples/JavaSieve/Package.swift | 8 +- .../JavaSieve/Sources/JavaSieve/main.swift | 2 +- .../Package.swift | 6 +- .../Sources/MySwiftLibrary/MySwiftClass.swift | 0 .../MySwiftLibrary/MySwiftLibrary.swift | 0 .../MySwiftLibrary/MySwiftStruct.swift | 0 .../jni/JNIImplementations.swift | 4 +- .../Sources/MySwiftLibrary/swift-java.config | 0 .../build.gradle | 0 .../ci-validate.sh | 0 .../gradle.properties | 0 .../gradlew | 0 .../gradlew.bat | 0 .../swiftkit/ffm/JavaToSwiftBenchmark.java | 0 .../swiftkit/ffm/StringPassingBenchmark.java | 0 .../com/example/swift/HelloJava2Swift.java | 0 .../com/example/swift/DataImportTest.java | 0 .../com/example/swift/MySwiftClassTest.java | 0 .../com/example/swift/MySwiftLibraryTest.java | 0 .../com/example/swift/OptionalImportTest.java | 0 .../example/swift/UnsignedNumbersTest.java | 0 .../swift/swiftkitffm/MySwiftClassTest.java | 0 .../swift/swiftkitffm/MySwiftStructTest.java | 0 .../org/swift/swiftkitffm/SwiftArenaTest.java | 0 .../Package.swift | 4 +- .../Sources/MySwiftLibrary/Alignment.swift | 0 .../Sources/MySwiftLibrary/Closures.swift | 0 .../Sources/MySwiftLibrary/MySwiftClass.swift | 2 +- .../MySwiftLibrary/MySwiftLibrary.swift | 0 .../MySwiftLibrary/MySwiftStruct.swift | 0 .../Sources/MySwiftLibrary/Optionals.swift | 2 +- .../Sources/MySwiftLibrary/Vehicle.swift | 0 .../Sources/MySwiftLibrary/swift-java.config | 0 .../build.gradle | 0 .../ci-validate.sh | 0 .../gradle.properties | 0 .../gradlew | 0 .../gradlew.bat | 0 .../com/example/swift/HelloJava2SwiftJNI.java | 0 .../com/example/swift/AlignmentEnumTest.java | 0 .../java/com/example/swift/ClosuresTest.java | 0 .../com/example/swift/MySwiftClassTest.java | 0 .../com/example/swift/MySwiftLibraryTest.java | 0 .../com/example/swift/MySwiftStructTest.java | 0 .../java/com/example/swift/OptionalsTest.java | 0 .../com/example/swift/VehicleEnumTest.java | 0 Sources/{JavaRuntime => CJNI}/dummy.c | 0 .../JavaRuntime.h => CJNI/include/CJNI.h} | 6 +- Sources/CJNI/include/module.modulemap | 4 + .../Common/TypeAnnotations.swift | 2 +- .../Configuration+Extensions.swift | 4 +- .../Convenience/String+Extensions.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 2 +- .../FFM/FFMSwift2JavaGenerator.swift | 2 +- .../JNI/JNIJavaTypeTranslator.swift | 2 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 2 +- ...wift2JavaGenerator+NativeTranslation.swift | 2 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../JNI/JNISwift2JavaGenerator.swift | 2 +- Sources/JExtractSwiftLib/Logger.swift | 4 +- Sources/JExtractSwiftLib/Swift2Java.swift | 4 +- .../Swift2JavaTranslator.swift | 2 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 2 +- .../DependencyResolver.swift | 6 +- .../swift-java.config | 2 +- Sources/JavaRuntime/include/module.modulemap | 4 - .../generated/BufferedInputStream.swift | 4 +- .../JavaIO}/generated/Charset.swift | 4 +- .../JavaIO}/generated/Closeable.swift | 4 +- .../JavaIO}/generated/File.swift | 4 +- .../JavaIO}/generated/FileDescriptor.swift | 4 +- .../JavaIO}/generated/FileReader.swift | 4 +- .../JavaIO}/generated/Flushable.swift | 4 +- .../JavaIO}/generated/InputStream.swift | 4 +- .../JavaIO}/generated/InputStreamReader.swift | 4 +- .../JavaIO}/generated/OutputStream.swift | 4 +- .../JavaIO}/generated/Path.swift | 4 +- .../JavaIO}/generated/Readable.swift | 4 +- .../JavaIO}/generated/Reader.swift | 4 +- .../JavaIO}/generated/StringReader.swift | 4 +- .../JavaIO}/generated/WatchService.swift | 4 +- .../JavaIO}/generated/Writer.swift | 4 +- .../JavaIO}/swift-java.config | 0 .../Constructor+Utilities.swift | 0 .../Executable+Utilities.swift | 2 +- .../JavaLangReflect}/Field+Utilities.swift | 0 .../JavaClass+Reflection.swift | 2 +- .../JavaLangReflect}/Method+Utilities.swift | 0 .../generated/AccessibleObject.swift | 4 +- .../generated/AnnotatedType.swift | 4 +- .../generated/Annotation.swift | 4 +- .../generated/Constructor.swift | 4 +- .../generated/Executable.swift | 6 +- .../JavaLangReflect}/generated/Field.swift | 6 +- .../generated/GenericArrayType.swift | 4 +- .../generated/GenericDeclaration.swift | 4 +- .../JavaLangReflect}/generated/Method.swift | 4 +- .../generated/Parameter.swift | 6 +- .../generated/ParameterizedType.swift | 4 +- .../JavaLangReflect}/generated/Type.swift | 4 +- .../generated/TypeVariable.swift | 4 +- .../generated/WildcardType.swift | 4 +- .../JavaLangReflect}/swift-java.config | 0 .../JavaNet}/generated/URI.swift | 4 +- .../JavaNet}/generated/URL.swift | 4 +- .../JavaNet}/generated/URLClassLoader.swift | 6 +- .../JavaNet}/generated/URLConnection.swift | 4 +- .../JavaNet}/swift-java.config | 0 .../JavaUtil}/JavaEnumeration+Sequence.swift | 0 .../JavaUtil}/JavaIterator+Iterator.swift | 2 +- .../JavaUtil}/List+Sequence.swift | 0 .../JavaUtil}/generated/ArrayDeque.swift | 4 +- .../JavaUtil}/generated/ArrayList.swift | 4 +- .../JavaUtil}/generated/BitSet.swift | 4 +- .../JavaUtil}/generated/Enumeration.swift | 4 +- .../JavaUtil}/generated/HashMap.swift | 4 +- .../JavaUtil}/generated/HashSet.swift | 4 +- .../JavaUtil}/generated/JavaCollection.swift | 4 +- .../JavaUtil}/generated/JavaDictionary.swift | 4 +- .../JavaUtil}/generated/JavaIterator.swift | 4 +- .../JavaUtil}/generated/JavaSet.swift | 4 +- .../JavaUtil}/generated/LinkedList.swift | 4 +- .../JavaUtil}/generated/List.swift | 4 +- .../JavaUtil}/generated/ListIterator.swift | 4 +- .../JavaUtil}/generated/PriorityQueue.swift | 4 +- .../JavaUtil}/generated/Queue.swift | 4 +- .../JavaUtil}/generated/RandomAccess.swift | 4 +- .../JavaUtil}/generated/Stack.swift | 4 +- .../JavaUtil}/generated/TreeMap.swift | 4 +- .../JavaUtil}/generated/TreeSet.swift | 4 +- .../JavaUtil}/swift-java.config | 0 .../generated/JavaBiConsumer.swift | 4 +- .../generated/JavaBiFunction.swift | 4 +- .../generated/JavaBiPredicate.swift | 4 +- .../generated/JavaBinaryOperator.swift | 4 +- .../generated/JavaBooleanSupplier.swift | 4 +- .../generated/JavaConsumer.swift | 4 +- .../generated/JavaDoubleBinaryOperator.swift | 4 +- .../generated/JavaDoubleConsumer.swift | 4 +- .../generated/JavaDoubleFunction.swift | 4 +- .../generated/JavaDoublePredicate.swift | 4 +- .../generated/JavaDoubleSupplier.swift | 4 +- .../generated/JavaDoubleToIntFunction.swift | 4 +- .../generated/JavaDoubleToLongFunction.swift | 4 +- .../generated/JavaDoubleUnaryOperator.swift | 4 +- .../generated/JavaFunction.swift | 4 +- .../generated/JavaIntBinaryOperator.swift | 4 +- .../generated/JavaIntConsumer.swift | 4 +- .../generated/JavaIntFunction.swift | 4 +- .../generated/JavaIntPredicate.swift | 4 +- .../generated/JavaIntSupplier.swift | 4 +- .../generated/JavaIntToDoubleFunction.swift | 4 +- .../generated/JavaIntToLongFunction.swift | 4 +- .../generated/JavaIntUnaryOperator.swift | 4 +- .../generated/JavaLongBinaryOperator.swift | 4 +- .../generated/JavaLongConsumer.swift | 4 +- .../generated/JavaLongFunction.swift | 4 +- .../generated/JavaLongPredicate.swift | 4 +- .../generated/JavaLongSupplier.swift | 4 +- .../generated/JavaLongToDoubleFunction.swift | 4 +- .../generated/JavaLongToIntFunction.swift | 4 +- .../generated/JavaLongUnaryOperator.swift | 4 +- .../generated/JavaObjDoubleConsumer.swift | 4 +- .../generated/JavaObjIntConsumer.swift | 4 +- .../generated/JavaObjLongConsumer.swift | 4 +- .../generated/JavaPredicate.swift | 4 +- .../generated/JavaSupplier.swift | 4 +- .../generated/JavaToDoubleBiFunction.swift | 4 +- .../generated/JavaToDoubleFunction.swift | 4 +- .../generated/JavaToIntBiFunction.swift | 4 +- .../generated/JavaToIntFunction.swift | 4 +- .../generated/JavaToLongBiFunction.swift | 4 +- .../generated/JavaToLongFunction.swift | 4 +- .../generated/JavaUnaryOperator.swift | 4 +- .../JavaUtilFunction}/swift-java.config | 0 .../JavaUtilJar}/generated/Attributes.swift | 6 +- .../JavaUtilJar}/generated/JarEntry.swift | 4 +- .../JavaUtilJar}/generated/JarFile.swift | 6 +- .../generated/JarInputStream.swift | 4 +- .../generated/JarOutputStream.swift | 4 +- .../JavaUtilJar}/generated/Manifest.swift | 4 +- .../JavaUtilJar}/generated/ZipEntry.swift | 4 +- .../JavaUtilJar}/swift-java.config | 0 .../JavaStdlib/README_PACKAGE_CONVENTION.md | 13 ++ .../AnyJavaObject.swift | 2 +- .../BridgedValues/JavaValue+Array.swift | 0 .../BridgedValues/JavaValue+Bool.swift | 0 .../JavaValue+FloatingPoint.swift | 0 .../BridgedValues/JavaValue+Integers.swift | 0 .../BridgedValues/JavaValue+String.swift | 0 .../Documentation.docc/JavaKit.md | 2 +- .../Exceptions/Exception+Error.swift | 0 .../Exceptions/ExceptionHandling.swift | 0 .../Exceptions/Throwable+Error.swift | 0 .../SwiftJava/Helpers/_JNIMethodIDCache.swift | 59 ++++++++ .../JavaClass+Initialization.swift | 2 +- .../JavaEnvironment.swift | 2 +- .../JavaKitVM/JavaVirtualMachine.swift | 0 .../JavaKitVM/LockedState.swift | 0 .../JavaKitVM/ThreadLocalStorage.swift | 0 .../JavaObject+Inheritance.swift | 2 +- .../JavaObject+MethodCalls.swift | 2 +- .../JavaObjectHolder.swift | 2 +- .../JavaRuntime+Reexport.swift | 2 +- .../{JavaKit => SwiftJava}/JavaValue.swift | 2 +- Sources/{JavaKit => SwiftJava}/Macros.swift | 14 +- .../Optional+JavaObject.swift | 2 +- .../Optional+JavaOptional.swift | 0 .../generated/Appendable.swift | 2 +- .../generated/CharSequence.swift | 2 +- .../generated/Exception.swift | 2 +- .../generated/JavaArray.swift | 2 +- .../generated/JavaBoolean.swift | 2 +- .../generated/JavaByte.swift | 2 +- .../generated/JavaCharacter.swift | 2 +- .../generated/JavaClass.swift | 2 +- .../generated/JavaClassLoader.swift | 2 +- .../generated/JavaDouble.swift | 2 +- .../generated/JavaError.swift | 2 +- .../generated/JavaFloat.swift | 2 +- .../generated/JavaInteger.swift | 2 +- .../generated/JavaLong.swift | 2 +- .../generated/JavaNumber.swift | 2 +- .../generated/JavaObject.swift | 2 +- .../generated/JavaOptional.swift | 2 +- .../generated/JavaOptionalDouble.swift | 2 +- .../generated/JavaOptionalInt.swift | 2 +- .../generated/JavaOptionalLong.swift | 2 +- .../generated/JavaShort.swift | 2 +- .../generated/JavaString.swift | 2 +- .../generated/JavaVoid.swift | 2 +- .../generated/RuntimeException.swift | 2 +- .../generated/Throwable.swift | 2 +- .../{JavaKit => SwiftJava}/swift-java.config | 0 .../Configuration.swift | 0 .../GenerationMode.swift | 0 .../GradleDependencyParsing.swift | 0 .../SwiftJavaCommandLineTool.md | 8 +- .../GenerationMode.swift | 0 .../ImplementsJavaMacro.swift | 0 .../JavaClassMacro.swift | 0 .../JavaFieldMacro.swift | 0 .../JavaMethodMacro.swift | 0 .../MacroErrors.swift | 0 .../SwiftJNIMacrosPlugin.swift} | 2 +- .../SwiftSyntaxUtils.swift | 0 .../TerminalColors.swift | 0 .../Commands/ConfigureCommand.swift | 14 +- .../Commands/JExtractCommand.swift | 12 +- .../Commands/ResolveCommand.swift | 14 +- .../Commands/WrapJavaCommand.swift | 10 +- Sources/SwiftJavaTool/CommonOptions.swift | 12 +- .../SwiftJavaTool/Java/JavaClassLoader.swift | 8 +- Sources/SwiftJavaTool/String+Extensions.swift | 10 +- Sources/SwiftJavaTool/SwiftJava.swift | 14 +- .../SwiftJavaBaseAsyncParsableCommand.swift | 14 +- .../JavaClassTranslator.swift | 4 +- .../JavaTranslator+Configuration.swift | 2 +- .../JavaTranslator+Validation.swift | 0 .../JavaTranslator.swift | 12 +- .../MethodVariance.swift | 4 +- .../OptionalKind.swift | 0 .../StringExtras.swift | 0 .../TranslationError.swift | 2 +- .../swiftkit/ffm/FFMSwiftInstanceCleanup.java | 4 +- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 27 ++++ .../Asserts/LoweringAssertions.swift | 2 +- .../Asserts/TextAssertions.swift | 2 +- .../FuncCallbackImportTests.swift | 2 +- .../FunctionDescriptorImportTests.swift | 2 +- .../InternalExtractTests.swift | 2 +- .../JNI/JNIUnsignedNumberTests.swift | 2 +- .../MemoryManagementModeTests.swift | 2 +- .../MethodImportTests.swift | 2 +- .../UnsignedNumberTests.swift | 2 +- .../GradleDependencyParsingTests.swift | 2 +- .../JavaClassMacroTests.swift | 2 +- .../BasicRuntimeTests.swift | 4 +- .../Java2SwiftTests.swift | 90 ++++++------ .../JavaTranslatorValidationTests.swift | 2 +- 296 files changed, 641 insertions(+), 527 deletions(-) delete mode 120000 Plugins/PluginsShared/JavaKitConfigurationShared create mode 120000 Plugins/PluginsShared/SwiftJavaConfigurationShared rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Package.swift (93%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/MySwiftClass.swift (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/MySwiftLibrary.swift (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/MySwiftStruct.swift (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/jni/JNIImplementations.swift (97%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/swift-java.config (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/build.gradle (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/ci-validate.sh (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/gradle.properties (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/gradlew (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/gradlew.bat (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/main/java/com/example/swift/HelloJava2Swift.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/DataImportTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/MySwiftClassTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/MySwiftLibraryTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/OptionalImportTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/UnsignedNumbersTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Package.swift (94%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Alignment.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Closures.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/MySwiftClass.swift (99%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/MySwiftLibrary.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/MySwiftStruct.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Optionals.swift (99%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Vehicle.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/swift-java.config (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/build.gradle (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/ci-validate.sh (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/gradle.properties (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/gradlew (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/gradlew.bat (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/main/java/com/example/swift/HelloJava2SwiftJNI.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/AlignmentEnumTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/ClosuresTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/MySwiftClassTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/MySwiftLibraryTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/MySwiftStructTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/OptionalsTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/VehicleEnumTest.java (100%) rename Sources/{JavaRuntime => CJNI}/dummy.c (100%) rename Sources/{JavaRuntime/include/JavaRuntime.h => CJNI/include/CJNI.h} (85%) create mode 100644 Sources/CJNI/include/module.modulemap delete mode 100644 Sources/JavaRuntime/include/module.modulemap rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/BufferedInputStream.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Charset.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Closeable.swift (82%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/File.swift (98%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/FileDescriptor.swift (94%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/FileReader.swift (95%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Flushable.swift (82%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/InputStream.swift (97%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/InputStreamReader.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/OutputStream.swift (95%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Path.swift (98%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Readable.swift (77%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Reader.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/StringReader.swift (95%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/WatchService.swift (85%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Writer.swift (97%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/swift-java.config (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Constructor+Utilities.swift (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Executable+Utilities.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Field+Utilities.swift (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/JavaClass+Reflection.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Method+Utilities.swift (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/AccessibleObject.swift (97%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/AnnotatedType.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Annotation.swift (91%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Constructor.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Executable.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Field.swift (97%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/GenericArrayType.swift (89%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/GenericDeclaration.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Method.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Parameter.swift (95%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/ParameterizedType.swift (91%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Type.swift (83%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/TypeVariable.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/WildcardType.swift (90%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/swift-java.config (100%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URI.swift (98%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URL.swift (97%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URLClassLoader.swift (93%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URLConnection.swift (99%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/swift-java.config (100%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/JavaEnumeration+Sequence.swift (100%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/JavaIterator+Iterator.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/List+Sequence.swift (100%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/ArrayDeque.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/ArrayList.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/BitSet.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/Enumeration.swift (90%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/HashMap.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/HashSet.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaCollection.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaDictionary.swift (95%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaIterator.swift (89%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaSet.swift (99%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/LinkedList.swift (99%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/List.swift (99%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/ListIterator.swift (94%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/PriorityQueue.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/Queue.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/RandomAccess.swift (79%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/Stack.swift (93%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/TreeMap.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/TreeSet.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/swift-java.config (100%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBiConsumer.swift (91%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBiFunction.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBiPredicate.swift (94%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBinaryOperator.swift (93%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBooleanSupplier.swift (85%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaConsumer.swift (90%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleBinaryOperator.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleConsumer.swift (89%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoublePredicate.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleSupplier.swift (85%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleToIntFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleToLongFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleUnaryOperator.swift (94%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaFunction.swift (95%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntBinaryOperator.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntConsumer.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntPredicate.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntSupplier.swift (84%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntToDoubleFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntToLongFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntUnaryOperator.swift (93%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongBinaryOperator.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongConsumer.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongPredicate.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongSupplier.swift (84%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongToDoubleFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongToIntFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongUnaryOperator.swift (93%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaObjDoubleConsumer.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaObjIntConsumer.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaObjLongConsumer.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaPredicate.swift (96%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaSupplier.swift (85%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToDoubleBiFunction.swift (89%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToDoubleFunction.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToIntBiFunction.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToIntFunction.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToLongBiFunction.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToLongFunction.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaUnaryOperator.swift (95%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/swift-java.config (100%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/Attributes.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarEntry.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarFile.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarInputStream.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarOutputStream.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/Manifest.swift (95%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/ZipEntry.swift (99%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/swift-java.config (100%) create mode 100644 Sources/JavaStdlib/README_PACKAGE_CONVENTION.md rename Sources/{JavaKit => SwiftJava}/AnyJavaObject.swift (99%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+Array.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+Bool.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+FloatingPoint.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+Integers.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+String.swift (100%) rename Sources/{JavaKit => SwiftJava}/Documentation.docc/JavaKit.md (99%) rename Sources/{JavaKit => SwiftJava}/Exceptions/Exception+Error.swift (100%) rename Sources/{JavaKit => SwiftJava}/Exceptions/ExceptionHandling.swift (100%) rename Sources/{JavaKit => SwiftJava}/Exceptions/Throwable+Error.swift (100%) create mode 100644 Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift rename Sources/{JavaKit => SwiftJava}/JavaClass+Initialization.swift (98%) rename Sources/{JavaKit => SwiftJava}/JavaEnvironment.swift (97%) rename Sources/{JavaKit => SwiftJava}/JavaKitVM/JavaVirtualMachine.swift (100%) rename Sources/{JavaKit => SwiftJava}/JavaKitVM/LockedState.swift (100%) rename Sources/{JavaKit => SwiftJava}/JavaKitVM/ThreadLocalStorage.swift (100%) rename Sources/{JavaKit => SwiftJava}/JavaObject+Inheritance.swift (98%) rename Sources/{JavaKit => SwiftJava}/JavaObject+MethodCalls.swift (99%) rename Sources/{JavaKit => SwiftJava}/JavaObjectHolder.swift (98%) rename Sources/{JavaKit => SwiftJava}/JavaRuntime+Reexport.swift (94%) rename Sources/{JavaKit => SwiftJava}/JavaValue.swift (99%) rename Sources/{JavaKit => SwiftJava}/Macros.swift (90%) rename Sources/{JavaKit => SwiftJava}/Optional+JavaObject.swift (99%) rename Sources/{JavaKit => SwiftJava}/Optional+JavaOptional.swift (100%) rename Sources/{JavaKit => SwiftJava}/generated/Appendable.swift (95%) rename Sources/{JavaKit => SwiftJava}/generated/CharSequence.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/Exception.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/JavaArray.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaBoolean.swift (98%) rename Sources/{JavaKit => SwiftJava}/generated/JavaByte.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaCharacter.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaClass.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaClassLoader.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaDouble.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaError.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/JavaFloat.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaInteger.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaLong.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaNumber.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/JavaObject.swift (97%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptional.swift (98%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptionalDouble.swift (98%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptionalInt.swift (97%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptionalLong.swift (97%) rename Sources/{JavaKit => SwiftJava}/generated/JavaShort.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaString.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaVoid.swift (92%) rename Sources/{JavaKit => SwiftJava}/generated/RuntimeException.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/Throwable.swift (98%) rename Sources/{JavaKit => SwiftJava}/swift-java.config (100%) rename Sources/{JavaKitConfigurationShared => SwiftJavaConfigurationShared}/Configuration.swift (100%) rename Sources/{JavaKitConfigurationShared => SwiftJavaConfigurationShared}/GenerationMode.swift (100%) rename Sources/{JavaKitConfigurationShared => SwiftJavaConfigurationShared}/GradleDependencyParsing.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/GenerationMode.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/ImplementsJavaMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/JavaClassMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/JavaFieldMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/JavaMethodMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/MacroErrors.swift (100%) rename Sources/{JavaKitMacros/JavaKitMacrosPlugin.swift => SwiftJavaMacros/SwiftJNIMacrosPlugin.swift} (93%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/SwiftSyntaxUtils.swift (100%) rename Sources/{JavaKitShared => SwiftJavaShared}/TerminalColors.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaClassTranslator.swift (99%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaTranslator+Configuration.swift (97%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaTranslator+Validation.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaTranslator.swift (98%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/MethodVariance.swift (98%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/OptionalKind.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/StringExtras.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/TranslationError.swift (97%) rename Tests/{JavaKitConfigurationSharedTests => SwiftJavaConfigurationSharedTests}/GradleDependencyParsingTests.swift (97%) rename Tests/{JavaKitMacroTests => SwiftJavaMacrosTests}/JavaClassMacroTests.swift (99%) rename Tests/{JavaKitTests => SwiftJavaTests}/BasicRuntimeTests.swift (98%) rename Tests/{SwiftJavaTests => SwiftJavaToolLibTests}/Java2SwiftTests.swift (89%) rename Tests/{SwiftJavaTests => SwiftJavaToolLibTests}/JavaTranslatorValidationTests.swift (98%) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b2d8ee7c..0a2b6949 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -166,8 +166,8 @@ jobs: 'JavaProbablyPrime', 'JavaSieve', 'SwiftAndJavaJarSampleLib', - 'SwiftKitSampleApp', - 'JExtractJNISampleApp', + 'SwiftJavaExtractFFMSampleApp', + 'SwiftJavaExtractJNISampleApp', ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} @@ -194,8 +194,8 @@ jobs: 'JavaProbablyPrime', 'JavaSieve', 'SwiftAndJavaJarSampleLib', - 'SwiftKitSampleApp', - 'JExtractJNISampleApp', + 'SwiftJavaExtractFFMSampleApp', + 'SwiftJavaExtractJNISampleApp', ] steps: - uses: actions/checkout@v4 diff --git a/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift b/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift index 25c67074..d52d3133 100644 --- a/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift +++ b/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift @@ -14,8 +14,8 @@ import Benchmark import Foundation -import JavaKit -import JavaKitNetwork +import SwiftJava +import JavaNet @MainActor let benchmarks = { var jvm: JavaVirtualMachine { diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index b8fbe580..955378fc 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -51,9 +51,9 @@ let package = Package( .executableTarget( name: "JavaApiCallBenchmarks", dependencies: [ - .product(name: "JavaRuntime", package: "swift-java"), - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitNetwork", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaNet", package: "swift-java"), .product(name: "Benchmark", package: "package-benchmark"), ], path: "Benchmarks/JavaApiCallBenchmarks", diff --git a/Package.swift b/Package.swift index 65737b2b..a30b9510 100644 --- a/Package.swift +++ b/Package.swift @@ -94,45 +94,45 @@ let package = Package( .macOS(.v15) ], products: [ - // ==== JavaKit (i.e. calling Java directly Swift utilities) + // ==== SwiftJava (i.e. calling Java directly Swift utilities) .library( - name: "JavaKit", - targets: ["JavaKit"] + name: "SwiftJava", + targets: ["SwiftJava"] ), .library( - name: "JavaRuntime", - targets: ["JavaRuntime"] + name: "CJNI", + targets: ["CJNI"] ), .library( - name: "JavaKitCollection", - targets: ["JavaKitCollection"] + name: "JavaUtil", + targets: ["JavaUtil"] ), .library( - name: "JavaKitFunction", - targets: ["JavaKitFunction"] + name: "JavaUtilFunction", + targets: ["JavaUtilFunction"] ), .library( - name: "JavaKitJar", - targets: ["JavaKitJar"] + name: "JavaUtilJar", + targets: ["JavaUtilJar"] ), .library( - name: "JavaKitNetwork", - targets: ["JavaKitNetwork"] + name: "JavaNet", + targets: ["JavaNet"] ), .library( - name: "JavaKitIO", - targets: ["JavaKitIO"] + name: "JavaIO", + targets: ["JavaIO"] ), .library( - name: "JavaKitReflection", - targets: ["JavaKitReflection"] + name: "JavaLangReflect", + targets: ["JavaLangReflect"] ), .library( @@ -212,13 +212,13 @@ let package = Package( .target( name: "SwiftJavaDocumentation", dependencies: [ - "JavaKit", + "SwiftJava", "SwiftKitSwift", ] ), .macro( - name: "JavaKitMacros", + name: "SwiftJavaMacros", dependencies: [ .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), @@ -235,12 +235,12 @@ let package = Package( ), .target( - name: "JavaKit", + name: "SwiftJava", dependencies: [ - "JavaRuntime", - "JavaKitMacros", + "CJNI", + "SwiftJavaMacros", "JavaTypes", - "JavaKitConfigurationShared", // for Configuration reading at runtime + "SwiftJavaConfigurationShared", // for Configuration reading at runtime ], exclude: ["swift-java.config"], swiftSettings: [ @@ -268,8 +268,9 @@ let package = Package( ] ), .target( - name: "JavaKitCollection", - dependencies: ["JavaKit"], + name: "JavaUtil", + dependencies: ["SwiftJava"], + path: "Sources/JavaStdlib/JavaUtil", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -277,8 +278,9 @@ let package = Package( ] ), .target( - name: "JavaKitFunction", - dependencies: ["JavaKit"], + name: "JavaUtilFunction", + dependencies: ["SwiftJava"], + path: "Sources/JavaStdlib/JavaUtilFunction", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -286,8 +288,9 @@ let package = Package( ] ), .target( - name: "JavaKitJar", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaUtilJar", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaUtilJar", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -295,8 +298,9 @@ let package = Package( ] ), .target( - name: "JavaKitNetwork", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaNet", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaNet", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -304,8 +308,9 @@ let package = Package( ] ), .target( - name: "JavaKitIO", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaIO", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaIO", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -313,8 +318,9 @@ let package = Package( ] ), .target( - name: "JavaKitReflection", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaLangReflect", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaLangReflect", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -353,7 +359,7 @@ let package = Package( ), .target( - name: "JavaRuntime", + name: "CJNI", swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -361,26 +367,26 @@ let package = Package( ), .target( - name: "JavaKitConfigurationShared" + name: "SwiftJavaConfigurationShared" ), .target( - name: "JavaKitShared" + name: "SwiftJavaShared" ), .target( - name: "SwiftJavaLib", + name: "SwiftJavaToolLib", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - "JavaKit", - "JavaKitJar", - "JavaKitReflection", - "JavaKitNetwork", + "SwiftJava", + "JavaUtilJar", + "JavaLangReflect", + "JavaNet", "JavaTypes", - "JavaKitShared", - "JavaKitConfigurationShared", + "SwiftJavaShared", + "SwiftJavaConfigurationShared", // .product(name: "Subprocess", package: "swift-subprocess") "_Subprocess", ], @@ -399,13 +405,13 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SystemPackage", package: "swift-system"), - "JavaKit", - "JavaKitJar", - "JavaKitNetwork", - "SwiftJavaLib", + "SwiftJava", + "JavaUtilJar", + "JavaNet", + "SwiftJavaToolLib", "JExtractSwiftLib", - "JavaKitShared", - "JavaKitConfigurationShared", + "SwiftJavaShared", + "SwiftJavaConfigurationShared", ], swiftSettings: [ .swiftLanguageMode(.v5), @@ -427,8 +433,8 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), "JavaTypes", - "JavaKitShared", - "JavaKitConfigurationShared", + "SwiftJavaShared", + "SwiftJavaConfigurationShared", ], swiftSettings: [ .swiftLanguageMode(.v5), @@ -445,8 +451,11 @@ let package = Package( ), .testTarget( - name: "JavaKitTests", - dependencies: ["JavaKit", "JavaKitNetwork"], + name: "SwiftJavaTests", + dependencies: [ + "SwiftJava", + "JavaNet" + ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -455,16 +464,18 @@ let package = Package( .testTarget( name: "JavaTypesTests", - dependencies: ["JavaTypes"], + dependencies: [ + "JavaTypes" + ], swiftSettings: [ .swiftLanguageMode(.v5) ] ), .testTarget( - name: "JavaKitMacroTests", + name: "SwiftJavaMacrosTests", dependencies: [ - "JavaKitMacros", + "SwiftJavaMacros", .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), ], swiftSettings: [ @@ -473,8 +484,10 @@ let package = Package( ), .testTarget( - name: "SwiftJavaTests", - dependencies: ["SwiftJavaLib"], + name: "SwiftJavaToolLibTests", + dependencies: [ + "SwiftJavaToolLib" + ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -482,8 +495,8 @@ let package = Package( ), .testTarget( - name: "JavaKitConfigurationSharedTests", - dependencies: ["JavaKitConfigurationShared"], + name: "SwiftJavaConfigurationSharedTests", + dependencies: ["SwiftJavaConfigurationShared"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 2778cebe..49ed3c4d 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -43,7 +43,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let sourceDir = target.directory.string - // The name of the configuration file JavaKit.config from the target for + // The name of the configuration file SwiftJava.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)") diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index 55955180..e12e13e8 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -32,7 +32,7 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // so we cannot eliminate this deprecation warning. let sourceDir = target.directory.string - // The name of the configuration file JavaKit.config from the target for + // The name of the configuration file SwiftJava.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 config: Configuration? diff --git a/Plugins/PluginsShared/JavaKitConfigurationShared b/Plugins/PluginsShared/JavaKitConfigurationShared deleted file mode 120000 index d5c765df..00000000 --- a/Plugins/PluginsShared/JavaKitConfigurationShared +++ /dev/null @@ -1 +0,0 @@ -../../Sources/JavaKitConfigurationShared \ No newline at end of file diff --git a/Plugins/PluginsShared/SwiftJavaConfigurationShared b/Plugins/PluginsShared/SwiftJavaConfigurationShared new file mode 120000 index 00000000..2af5fd01 --- /dev/null +++ b/Plugins/PluginsShared/SwiftJavaConfigurationShared @@ -0,0 +1 @@ +../../Sources/SwiftJavaConfigurationShared \ No newline at end of file diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index cbbbe425..57641eff 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -34,7 +34,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // so we cannot eliminate this deprecation warning. let sourceDir = target.directory.string - // The name of the configuration file JavaKit.config from the target for + // The name of the configuration file SwiftJava.config from the target for // which we are generating Swift wrappers for Java classes. let configFile = URL(filePath: sourceDir) .appending(path: SwiftJavaConfigFileName) diff --git a/README.md b/README.md index 940b478c..9f676905 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ To view the rendered docc documentation you can use the docc preview command: xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc # OR JavaKit to view JavaKit documentation: -# xcrun docc preview Sources/JavaKit/Documentation.docc +# xcrun docc preview Sources/SwiftJNI/Documentation.docc # ======================================== # Starting Local Preview Server diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index 506b8c93..c5ae97c7 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -64,9 +64,9 @@ let package = Package( .executableTarget( name: "JavaDependencySample", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaRuntime", package: "swift-java"), - .product(name: "JavaKitFunction", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), + .product(name: "JavaUtilFunction", package: "swift-java"), "JavaCommonsCSV" ], exclude: ["swift-java.config"], @@ -82,11 +82,11 @@ let package = Package( .target( name: "JavaCommonsCSV", dependencies: [ - .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"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtilFunction", package: "swift-java"), + .product(name: "JavaUtil", package: "swift-java"), + .product(name: "JavaIO", package: "swift-java"), + .product(name: "JavaNet", package: "swift-java"), ], exclude: ["swift-java.config"], swiftSettings: [ diff --git a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift index c75cf553..13ea6eed 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift +++ b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitFunction -import JavaKitIO -import JavaKitConfigurationShared +import SwiftJava +import JavaUtilFunction +import JavaIO +import SwiftJavaConfigurationShared import Foundation // Import the commons-csv library wrapper: diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 1b545819..32451e92 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -65,9 +65,9 @@ let package = Package( .target( name: "JavaKitExample", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitFunction", package: "swift-java"), - .product(name: "JavaKitJar", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtilFunction", package: "swift-java"), + .product(name: "JavaUtilJar", package: "swift-java"), ], swiftSettings: [ .swiftLanguageMode(.v5), diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift index f074fee6..44f811d3 100644 --- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift +++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitFunction +import SwiftJava +import JavaUtilFunction enum SwiftWrappedError: Error { case message(String) diff --git a/Samples/JavaProbablyPrime/Package.swift b/Samples/JavaProbablyPrime/Package.swift index 9f0ecff2..3ebf8fcb 100644 --- a/Samples/JavaProbablyPrime/Package.swift +++ b/Samples/JavaProbablyPrime/Package.swift @@ -29,8 +29,8 @@ let package = Package( .executableTarget( name: "JavaProbablyPrime", dependencies: [ - .product(name: "JavaKitCollection", package: "swift-java"), - .product(name: "JavaKit", package: "swift-java"), + .product(name: "JavaUtil", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), .product(name: "ArgumentParser", package: "swift-argument-parser"), ], swiftSettings: [ diff --git a/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift b/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift index 07f701bd..c070c87a 100644 --- a/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift +++ b/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import ArgumentParser -import JavaKit +import SwiftJava @main struct ProbablyPrime: ParsableCommand { diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift index ee61a021..c34d8331 100644 --- a/Samples/JavaSieve/Package.swift +++ b/Samples/JavaSieve/Package.swift @@ -54,8 +54,8 @@ let package = Package( .target( name: "JavaMath", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitJar", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtilJar", package: "swift-java"), ], exclude: ["swift-java.config"], swiftSettings: [ @@ -72,8 +72,8 @@ let package = Package( name: "JavaSieve", dependencies: [ "JavaMath", - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitCollection", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtil", package: "swift-java"), ], exclude: ["swift-java.config"], swiftSettings: [ diff --git a/Samples/JavaSieve/Sources/JavaSieve/main.swift b/Samples/JavaSieve/Sources/JavaSieve/main.swift index feec792d..470ee5c7 100644 --- a/Samples/JavaSieve/Sources/JavaSieve/main.swift +++ b/Samples/JavaSieve/Sources/JavaSieve/main.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava import JavaMath let jvm = try JavaVirtualMachine.shared() diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift similarity index 93% rename from Samples/SwiftKitSampleApp/Package.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Package.swift index 8501062b..30630a01 100644 --- a/Samples/SwiftKitSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -41,7 +41,7 @@ let javaIncludePath = "\(javaHome)/include" #endif let package = Package( - name: "SwiftKitSampleApp", + name: "SwiftJavaExtractFFMSampleApp", platforms: [ .macOS(.v15), .iOS(.v18), @@ -63,8 +63,8 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaRuntime", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift similarity index 97% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift index ef0e2e58..481265d5 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("com.example.swift.HelloJava2Swift") open class HelloJava2Swift: JavaObject { diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/swift-java.config rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle similarity index 100% rename from Samples/SwiftKitSampleApp/build.gradle rename to Samples/SwiftJavaExtractFFMSampleApp/build.gradle diff --git a/Samples/JExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh similarity index 100% rename from Samples/JExtractJNISampleApp/ci-validate.sh rename to Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh diff --git a/Samples/JExtractJNISampleApp/gradle.properties b/Samples/SwiftJavaExtractFFMSampleApp/gradle.properties similarity index 100% rename from Samples/JExtractJNISampleApp/gradle.properties rename to Samples/SwiftJavaExtractFFMSampleApp/gradle.properties diff --git a/Samples/JExtractJNISampleApp/gradlew b/Samples/SwiftJavaExtractFFMSampleApp/gradlew similarity index 100% rename from Samples/JExtractJNISampleApp/gradlew rename to Samples/SwiftJavaExtractFFMSampleApp/gradlew diff --git a/Samples/JExtractJNISampleApp/gradlew.bat b/Samples/SwiftJavaExtractFFMSampleApp/gradlew.bat similarity index 100% rename from Samples/JExtractJNISampleApp/gradlew.bat rename to Samples/SwiftJavaExtractFFMSampleApp/gradlew.bat diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java b/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java b/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/DataImportTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/DataImportTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/OptionalImportTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/OptionalImportTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java diff --git a/Samples/JExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift similarity index 94% rename from Samples/JExtractJNISampleApp/Package.swift rename to Samples/SwiftJavaExtractJNISampleApp/Package.swift index 8c7ce856..0d2d2fa8 100644 --- a/Samples/JExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -60,8 +60,8 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaRuntime", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift similarity index 99% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 9a78c3c2..4b334a5e 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava public class MySwiftClass { public let x: Int64 diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift similarity index 99% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift index 673ecb0b..3e122102 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava public func optionalBool(input: Optional) -> Bool? { return input diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle similarity index 100% rename from Samples/JExtractJNISampleApp/build.gradle rename to Samples/SwiftJavaExtractJNISampleApp/build.gradle diff --git a/Samples/SwiftKitSampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh similarity index 100% rename from Samples/SwiftKitSampleApp/ci-validate.sh rename to Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh diff --git a/Samples/SwiftKitSampleApp/gradle.properties b/Samples/SwiftJavaExtractJNISampleApp/gradle.properties similarity index 100% rename from Samples/SwiftKitSampleApp/gradle.properties rename to Samples/SwiftJavaExtractJNISampleApp/gradle.properties diff --git a/Samples/SwiftKitSampleApp/gradlew b/Samples/SwiftJavaExtractJNISampleApp/gradlew similarity index 100% rename from Samples/SwiftKitSampleApp/gradlew rename to Samples/SwiftJavaExtractJNISampleApp/gradlew diff --git a/Samples/SwiftKitSampleApp/gradlew.bat b/Samples/SwiftJavaExtractJNISampleApp/gradlew.bat similarity index 100% rename from Samples/SwiftKitSampleApp/gradlew.bat rename to Samples/SwiftJavaExtractJNISampleApp/gradlew.bat diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java rename to Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java diff --git a/Sources/JavaRuntime/dummy.c b/Sources/CJNI/dummy.c similarity index 100% rename from Sources/JavaRuntime/dummy.c rename to Sources/CJNI/dummy.c diff --git a/Sources/JavaRuntime/include/JavaRuntime.h b/Sources/CJNI/include/CJNI.h similarity index 85% rename from Sources/JavaRuntime/include/JavaRuntime.h rename to Sources/CJNI/include/CJNI.h index 02bf548e..bef6e7ff 100644 --- a/Sources/JavaRuntime/include/JavaRuntime.h +++ b/Sources/CJNI/include/CJNI.h @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// -#ifndef Swift_JavaRuntime_h -#define Swift_JavaRuntime_h +#ifndef Swift_CJNI_h +#define Swift_CJNI_h #include -#endif /* Swift_JavaRuntime_h */ +#endif /* Swift_CJNI_h */ diff --git a/Sources/CJNI/include/module.modulemap b/Sources/CJNI/include/module.modulemap new file mode 100644 index 00000000..c71f30c2 --- /dev/null +++ b/Sources/CJNI/include/module.modulemap @@ -0,0 +1,4 @@ +module CJNI { + umbrella header "CJNI.h" + export * +} diff --git a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift index 70a86e82..0896e4be 100644 --- a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift +++ b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared /// Determine if the given type needs any extra annotations that should be included /// in Java sources when the corresponding Java type is rendered. diff --git a/Sources/JExtractSwiftLib/Configuration+Extensions.swift b/Sources/JExtractSwiftLib/Configuration+Extensions.swift index d85cf447..042d8610 100644 --- a/Sources/JExtractSwiftLib/Configuration+Extensions.swift +++ b/Sources/JExtractSwiftLib/Configuration+Extensions.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared -import JavaTypes // TODO: this should become SwiftJavaConfigurationShared +import SwiftJavaConfigurationShared +import JavaTypes extension Configuration { public var effectiveUnsignedNumericsMode: UnsignedNumericsMode { diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 25c46366..82ce5c1c 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -66,7 +66,7 @@ extension String { .joined() } - /// Looks up self as a JavaKit wrapped class name and converts it + /// Looks up self as a SwiftJava wrapped class name and converts it /// into a `JavaType.class` if it exists in `lookupTable`. func parseJavaClassFromJavaKitName(in lookupTable: [String: String]) -> JavaType? { guard let canonicalJavaName = lookupTable[self] else { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 57f947a8..00fa60e7 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension FFMSwift2JavaGenerator { func translatedDecl( diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 9c9154a4..c9a5028b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -15,7 +15,7 @@ import JavaTypes import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import struct Foundation.URL package class FFMSwift2JavaGenerator: Swift2JavaGenerator { diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index fe10ef72..983396ac 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared enum JNIJavaTypeTranslator { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 0e169ec6..073883a2 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension JNISwift2JavaGenerator { func translatedDecl( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index f2e9522b..03273e68 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension JNISwift2JavaGenerator { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 6b8c435a..53679783 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -408,8 +408,8 @@ extension JNISwift2JavaGenerator { """ // Generated by swift-java - import JavaKit - import JavaRuntime + import SwiftJava + import CJNI """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 60ec1b8b..f9b99f6b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared /// A table that where keys are Swift class names and the values are /// the fully qualified canoical names. diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift index 5bffdc8c..6ef04651 100644 --- a/Sources/JExtractSwiftLib/Logger.swift +++ b/Sources/JExtractSwiftLib/Logger.swift @@ -15,7 +15,7 @@ import Foundation import SwiftSyntax import ArgumentParser -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared // Placeholder for some better logger, we could depend on swift-log public struct Logger { @@ -114,7 +114,7 @@ public struct Logger { } extension Logger { - public typealias Level = JavaKitConfigurationShared.LogLevel + public typealias Level = SwiftJavaConfigurationShared.LogLevel } extension Logger.Level: ExpressibleByArgument { diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 5a73e328..e6271993 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -15,8 +15,8 @@ import Foundation import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitShared -import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared +import SwiftJavaShared +import SwiftJavaConfigurationShared public struct SwiftToJava { let config: Configuration diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 300b979f..02049ed4 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -16,7 +16,7 @@ import Foundation import JavaTypes import SwiftBasicFormat import SwiftParser -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import SwiftSyntax /// Takes swift interfaces and translates them into Java used to access those. diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index bc6ac032..9b183cb6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -15,7 +15,7 @@ import Foundation import SwiftParser import SwiftSyntax -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared final class Swift2JavaVisitor { let translator: Swift2JavaTranslator diff --git a/Sources/JavaKitDependencyResolver/DependencyResolver.swift b/Sources/JavaKitDependencyResolver/DependencyResolver.swift index 697e3d2a..1ecc8dc5 100644 --- a/Sources/JavaKitDependencyResolver/DependencyResolver.swift +++ b/Sources/JavaKitDependencyResolver/DependencyResolver.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI -@JavaInterface("org.swift.javakit.dependencies.DependencyResolver") +@JavaInterface("org.swift.jni.dependencies.DependencyResolver") public struct DependencyResolver { } diff --git a/Sources/JavaKitDependencyResolver/swift-java.config b/Sources/JavaKitDependencyResolver/swift-java.config index 6ea5aa87..3803b5f0 100644 --- a/Sources/JavaKitDependencyResolver/swift-java.config +++ b/Sources/JavaKitDependencyResolver/swift-java.config @@ -1,5 +1,5 @@ { "dependencies": [ - ":JavaKit", + ":SwiftJNI", ] } diff --git a/Sources/JavaRuntime/include/module.modulemap b/Sources/JavaRuntime/include/module.modulemap deleted file mode 100644 index 2c0d4a98..00000000 --- a/Sources/JavaRuntime/include/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module JavaRuntime { - umbrella header "JavaRuntime.h" - export * -} diff --git a/Sources/JavaKitIO/generated/BufferedInputStream.swift b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift similarity index 96% rename from Sources/JavaKitIO/generated/BufferedInputStream.swift rename to Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift index 8ea95eeb..b4a13494 100644 --- a/Sources/JavaKitIO/generated/BufferedInputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.BufferedInputStream") open class BufferedInputStream: InputStream { diff --git a/Sources/JavaKitIO/generated/Charset.swift b/Sources/JavaStdlib/JavaIO/generated/Charset.swift similarity index 96% rename from Sources/JavaKitIO/generated/Charset.swift rename to Sources/JavaStdlib/JavaIO/generated/Charset.swift index fda054ef..f3b9126c 100644 --- a/Sources/JavaKitIO/generated/Charset.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Charset.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.nio.charset.Charset") open class Charset: JavaObject { diff --git a/Sources/JavaKitIO/generated/Closeable.swift b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift similarity index 82% rename from Sources/JavaKitIO/generated/Closeable.swift rename to Sources/JavaStdlib/JavaIO/generated/Closeable.swift index 6da8e2a9..9e68c58b 100644 --- a/Sources/JavaKitIO/generated/Closeable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.io.Closeable") public struct Closeable { diff --git a/Sources/JavaKitIO/generated/File.swift b/Sources/JavaStdlib/JavaIO/generated/File.swift similarity index 98% rename from Sources/JavaKitIO/generated/File.swift rename to Sources/JavaStdlib/JavaIO/generated/File.swift index 5cbdb70e..11831b5e 100644 --- a/Sources/JavaKitIO/generated/File.swift +++ b/Sources/JavaStdlib/JavaIO/generated/File.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.File") open class File: JavaObject { diff --git a/Sources/JavaKitIO/generated/FileDescriptor.swift b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift similarity index 94% rename from Sources/JavaKitIO/generated/FileDescriptor.swift rename to Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift index 81490e46..36f1686c 100644 --- a/Sources/JavaKitIO/generated/FileDescriptor.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.FileDescriptor") open class FileDescriptor: JavaObject { diff --git a/Sources/JavaKitIO/generated/FileReader.swift b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift similarity index 95% rename from Sources/JavaKitIO/generated/FileReader.swift rename to Sources/JavaStdlib/JavaIO/generated/FileReader.swift index 5254bdd0..ed0898c7 100644 --- a/Sources/JavaKitIO/generated/FileReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.FileReader") open class FileReader: InputStreamReader { diff --git a/Sources/JavaKitIO/generated/Flushable.swift b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift similarity index 82% rename from Sources/JavaKitIO/generated/Flushable.swift rename to Sources/JavaStdlib/JavaIO/generated/Flushable.swift index daf621f6..59854369 100644 --- a/Sources/JavaKitIO/generated/Flushable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.io.Flushable") public struct Flushable { diff --git a/Sources/JavaKitIO/generated/InputStream.swift b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift similarity index 97% rename from Sources/JavaKitIO/generated/InputStream.swift rename to Sources/JavaStdlib/JavaIO/generated/InputStream.swift index 971a610b..d521f265 100644 --- a/Sources/JavaKitIO/generated/InputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.InputStream", implements: Closeable.self) open class InputStream: JavaObject { diff --git a/Sources/JavaKitIO/generated/InputStreamReader.swift b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift similarity index 96% rename from Sources/JavaKitIO/generated/InputStreamReader.swift rename to Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift index 2766aeba..faa04eda 100644 --- a/Sources/JavaKitIO/generated/InputStreamReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.InputStreamReader") open class InputStreamReader: Reader { diff --git a/Sources/JavaKitIO/generated/OutputStream.swift b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift similarity index 95% rename from Sources/JavaKitIO/generated/OutputStream.swift rename to Sources/JavaStdlib/JavaIO/generated/OutputStream.swift index e169bfd0..1d3ec356 100644 --- a/Sources/JavaKitIO/generated/OutputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.OutputStream", implements: Closeable.self, Flushable.self) open class OutputStream: JavaObject { diff --git a/Sources/JavaKitIO/generated/Path.swift b/Sources/JavaStdlib/JavaIO/generated/Path.swift similarity index 98% rename from Sources/JavaKitIO/generated/Path.swift rename to Sources/JavaStdlib/JavaIO/generated/Path.swift index e93d0381..87956f10 100644 --- a/Sources/JavaKitIO/generated/Path.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Path.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.nio.file.Path") public struct Path { diff --git a/Sources/JavaKitIO/generated/Readable.swift b/Sources/JavaStdlib/JavaIO/generated/Readable.swift similarity index 77% rename from Sources/JavaKitIO/generated/Readable.swift rename to Sources/JavaStdlib/JavaIO/generated/Readable.swift index 16825989..25f48221 100644 --- a/Sources/JavaKitIO/generated/Readable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Readable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.Readable") public struct Readable { diff --git a/Sources/JavaKitIO/generated/Reader.swift b/Sources/JavaStdlib/JavaIO/generated/Reader.swift similarity index 96% rename from Sources/JavaKitIO/generated/Reader.swift rename to Sources/JavaStdlib/JavaIO/generated/Reader.swift index 2f6cdfe2..e133f741 100644 --- a/Sources/JavaKitIO/generated/Reader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Reader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.Reader", implements: Readable.self, Closeable.self) open class Reader: JavaObject { diff --git a/Sources/JavaKitIO/generated/StringReader.swift b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift similarity index 95% rename from Sources/JavaKitIO/generated/StringReader.swift rename to Sources/JavaStdlib/JavaIO/generated/StringReader.swift index e2af1166..aa3efd38 100644 --- a/Sources/JavaKitIO/generated/StringReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.StringReader") open class StringReader: Reader { diff --git a/Sources/JavaKitIO/generated/WatchService.swift b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift similarity index 85% rename from Sources/JavaKitIO/generated/WatchService.swift rename to Sources/JavaStdlib/JavaIO/generated/WatchService.swift index 20bca06f..0b44c4fe 100644 --- a/Sources/JavaKitIO/generated/WatchService.swift +++ b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.nio.file.WatchService", extends: Closeable.self) public struct WatchService { diff --git a/Sources/JavaKitIO/generated/Writer.swift b/Sources/JavaStdlib/JavaIO/generated/Writer.swift similarity index 97% rename from Sources/JavaKitIO/generated/Writer.swift rename to Sources/JavaStdlib/JavaIO/generated/Writer.swift index 5e3fdff2..7712a1c0 100644 --- a/Sources/JavaKitIO/generated/Writer.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Writer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.Writer", implements: Appendable.self, Closeable.self, Flushable.self) open class Writer: JavaObject { diff --git a/Sources/JavaKitIO/swift-java.config b/Sources/JavaStdlib/JavaIO/swift-java.config similarity index 100% rename from Sources/JavaKitIO/swift-java.config rename to Sources/JavaStdlib/JavaIO/swift-java.config diff --git a/Sources/JavaKitReflection/Constructor+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift similarity index 100% rename from Sources/JavaKitReflection/Constructor+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift diff --git a/Sources/JavaKitReflection/Executable+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Executable+Utilities.swift similarity index 98% rename from Sources/JavaKitReflection/Executable+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Executable+Utilities.swift index 2b0a8a2d..bf70f114 100644 --- a/Sources/JavaKitReflection/Executable+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Executable+Utilities.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava extension Executable { /// Whether this executable throws any checked exception. diff --git a/Sources/JavaKitReflection/Field+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Field+Utilities.swift similarity index 100% rename from Sources/JavaKitReflection/Field+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Field+Utilities.swift diff --git a/Sources/JavaKitReflection/JavaClass+Reflection.swift b/Sources/JavaStdlib/JavaLangReflect/JavaClass+Reflection.swift similarity index 98% rename from Sources/JavaKitReflection/JavaClass+Reflection.swift rename to Sources/JavaStdlib/JavaLangReflect/JavaClass+Reflection.swift index 1f2120e0..29e24be5 100644 --- a/Sources/JavaKitReflection/JavaClass+Reflection.swift +++ b/Sources/JavaStdlib/JavaLangReflect/JavaClass+Reflection.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava // TODO: We should be able to autogenerate this as an extension based on // knowing that JavaClass was defined elsewhere. diff --git a/Sources/JavaKitReflection/Method+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift similarity index 100% rename from Sources/JavaKitReflection/Method+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift diff --git a/Sources/JavaKitReflection/generated/AccessibleObject.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift similarity index 97% rename from Sources/JavaKitReflection/generated/AccessibleObject.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift index 84dcc4c2..c118f78b 100644 --- a/Sources/JavaKitReflection/generated/AccessibleObject.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.lang.reflect.AccessibleObject") open class AccessibleObject: JavaObject { diff --git a/Sources/JavaKitReflection/generated/AnnotatedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift similarity index 96% rename from Sources/JavaKitReflection/generated/AnnotatedType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift index ca9b9994..cc5b418c 100644 --- a/Sources/JavaKitReflection/generated/AnnotatedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.AnnotatedType") public struct AnnotatedType { diff --git a/Sources/JavaKitReflection/generated/Annotation.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift similarity index 91% rename from Sources/JavaKitReflection/generated/Annotation.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift index 269c0f9e..66ab49cc 100644 --- a/Sources/JavaKitReflection/generated/Annotation.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.annotation.Annotation") public struct Annotation { diff --git a/Sources/JavaKitReflection/generated/Constructor.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift similarity index 98% rename from Sources/JavaKitReflection/generated/Constructor.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift index 263d767d..76f49f11 100644 --- a/Sources/JavaKitReflection/generated/Constructor.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.lang.reflect.Constructor") open class Constructor: Executable { diff --git a/Sources/JavaKitReflection/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift similarity index 96% rename from Sources/JavaKitReflection/generated/Executable.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 6a1f5b75..31799784 100644 --- a/Sources/JavaKitReflection/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.lang.reflect.Executable", implements: GenericDeclaration.self) open class Executable: AccessibleObject { diff --git a/Sources/JavaKitReflection/generated/Field.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift similarity index 97% rename from Sources/JavaKitReflection/generated/Field.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Field.swift index c67e56ff..2c4a49e9 100644 --- a/Sources/JavaKitReflection/generated/Field.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.lang.reflect.Field") open class Field: AccessibleObject { diff --git a/Sources/JavaKitReflection/generated/GenericArrayType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift similarity index 89% rename from Sources/JavaKitReflection/generated/GenericArrayType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift index 6f96a0d2..75ad2c75 100644 --- a/Sources/JavaKitReflection/generated/GenericArrayType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.GenericArrayType", extends: Type.self) public struct GenericArrayType { diff --git a/Sources/JavaKitReflection/generated/GenericDeclaration.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift similarity index 96% rename from Sources/JavaKitReflection/generated/GenericDeclaration.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift index 7a86bd8a..bec84aa5 100644 --- a/Sources/JavaKitReflection/generated/GenericDeclaration.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.GenericDeclaration") public struct GenericDeclaration { diff --git a/Sources/JavaKitReflection/generated/Method.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift similarity index 98% rename from Sources/JavaKitReflection/generated/Method.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Method.swift index 46183f4f..4489cf24 100644 --- a/Sources/JavaKitReflection/generated/Method.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.lang.reflect.Method") open class Method: Executable { diff --git a/Sources/JavaKitReflection/generated/Parameter.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift similarity index 95% rename from Sources/JavaKitReflection/generated/Parameter.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift index 30f2d628..6dea46fd 100644 --- a/Sources/JavaKitReflection/generated/Parameter.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.lang.reflect.Parameter") open class Parameter: JavaObject { diff --git a/Sources/JavaKitReflection/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift similarity index 91% rename from Sources/JavaKitReflection/generated/ParameterizedType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index 3e413714..a427a2a5 100644 --- a/Sources/JavaKitReflection/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.ParameterizedType", extends: Type.self) public struct ParameterizedType { diff --git a/Sources/JavaKitReflection/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift similarity index 83% rename from Sources/JavaKitReflection/generated/Type.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index ea6376cb..05e8687e 100644 --- a/Sources/JavaKitReflection/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.Type") public struct Type { diff --git a/Sources/JavaKitReflection/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift similarity index 96% rename from Sources/JavaKitReflection/generated/TypeVariable.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index c925cdcf..e6a7899c 100644 --- a/Sources/JavaKitReflection/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) public struct TypeVariable { diff --git a/Sources/JavaKitReflection/generated/WildcardType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift similarity index 90% rename from Sources/JavaKitReflection/generated/WildcardType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift index e1551f50..2514495a 100644 --- a/Sources/JavaKitReflection/generated/WildcardType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.WildcardType", extends: Type.self) public struct WildcardType { diff --git a/Sources/JavaKitReflection/swift-java.config b/Sources/JavaStdlib/JavaLangReflect/swift-java.config similarity index 100% rename from Sources/JavaKitReflection/swift-java.config rename to Sources/JavaStdlib/JavaLangReflect/swift-java.config diff --git a/Sources/JavaKitNetwork/generated/URI.swift b/Sources/JavaStdlib/JavaNet/generated/URI.swift similarity index 98% rename from Sources/JavaKitNetwork/generated/URI.swift rename to Sources/JavaStdlib/JavaNet/generated/URI.swift index 000e29ca..f1a6c078 100644 --- a/Sources/JavaKitNetwork/generated/URI.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URI.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.net.URI") open class URI: JavaObject { diff --git a/Sources/JavaKitNetwork/generated/URL.swift b/Sources/JavaStdlib/JavaNet/generated/URL.swift similarity index 97% rename from Sources/JavaKitNetwork/generated/URL.swift rename to Sources/JavaStdlib/JavaNet/generated/URL.swift index ed0dee38..9140a1d9 100644 --- a/Sources/JavaKitNetwork/generated/URL.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URL.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.net.URL") open class URL: JavaObject { diff --git a/Sources/JavaKitNetwork/generated/URLClassLoader.swift b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift similarity index 93% rename from Sources/JavaKitNetwork/generated/URLClassLoader.swift rename to Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift index eac9c14c..1750f197 100644 --- a/Sources/JavaKitNetwork/generated/URLClassLoader.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.net.URLClassLoader") open class URLClassLoader: JavaObject { diff --git a/Sources/JavaKitNetwork/generated/URLConnection.swift b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift similarity index 99% rename from Sources/JavaKitNetwork/generated/URLConnection.swift rename to Sources/JavaStdlib/JavaNet/generated/URLConnection.swift index 94fd6f52..6a93edd1 100644 --- a/Sources/JavaKitNetwork/generated/URLConnection.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.net.URLConnection") public struct URLConnection { diff --git a/Sources/JavaKitNetwork/swift-java.config b/Sources/JavaStdlib/JavaNet/swift-java.config similarity index 100% rename from Sources/JavaKitNetwork/swift-java.config rename to Sources/JavaStdlib/JavaNet/swift-java.config diff --git a/Sources/JavaKitCollection/JavaEnumeration+Sequence.swift b/Sources/JavaStdlib/JavaUtil/JavaEnumeration+Sequence.swift similarity index 100% rename from Sources/JavaKitCollection/JavaEnumeration+Sequence.swift rename to Sources/JavaStdlib/JavaUtil/JavaEnumeration+Sequence.swift diff --git a/Sources/JavaKitCollection/JavaIterator+Iterator.swift b/Sources/JavaStdlib/JavaUtil/JavaIterator+Iterator.swift similarity index 97% rename from Sources/JavaKitCollection/JavaIterator+Iterator.swift rename to Sources/JavaStdlib/JavaUtil/JavaIterator+Iterator.swift index add2a48b..ad427527 100644 --- a/Sources/JavaKitCollection/JavaIterator+Iterator.swift +++ b/Sources/JavaStdlib/JavaUtil/JavaIterator+Iterator.swift @@ -11,7 +11,7 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava extension JavaIterator: IteratorProtocol { public typealias Element = E diff --git a/Sources/JavaKitCollection/List+Sequence.swift b/Sources/JavaStdlib/JavaUtil/List+Sequence.swift similarity index 100% rename from Sources/JavaKitCollection/List+Sequence.swift rename to Sources/JavaStdlib/JavaUtil/List+Sequence.swift diff --git a/Sources/JavaKitCollection/generated/ArrayDeque.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift similarity index 98% rename from Sources/JavaKitCollection/generated/ArrayDeque.swift rename to Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift index 79a1e20b..f0d257af 100644 --- a/Sources/JavaKitCollection/generated/ArrayDeque.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.ArrayDeque") open class ArrayDeque: JavaObject { diff --git a/Sources/JavaKitCollection/generated/ArrayList.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift similarity index 98% rename from Sources/JavaKitCollection/generated/ArrayList.swift rename to Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift index 6748640b..b39ada56 100644 --- a/Sources/JavaKitCollection/generated/ArrayList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.ArrayList", implements: List.self, RandomAccess.self) open class ArrayList: JavaObject { diff --git a/Sources/JavaKitCollection/generated/BitSet.swift b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift similarity index 98% rename from Sources/JavaKitCollection/generated/BitSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/BitSet.swift index d5211c28..2d89ba5a 100644 --- a/Sources/JavaKitCollection/generated/BitSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.BitSet") open class BitSet: JavaObject { diff --git a/Sources/JavaKitCollection/generated/Enumeration.swift b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift similarity index 90% rename from Sources/JavaKitCollection/generated/Enumeration.swift rename to Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift index 492cc3b2..0229e16d 100644 --- a/Sources/JavaKitCollection/generated/Enumeration.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Enumeration") public struct Enumeration { diff --git a/Sources/JavaKitCollection/generated/HashMap.swift b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift similarity index 98% rename from Sources/JavaKitCollection/generated/HashMap.swift rename to Sources/JavaStdlib/JavaUtil/generated/HashMap.swift index 424dfbb9..6fd38bc4 100644 --- a/Sources/JavaKitCollection/generated/HashMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.HashMap") open class HashMap: JavaObject { diff --git a/Sources/JavaKitCollection/generated/HashSet.swift b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift similarity index 97% rename from Sources/JavaKitCollection/generated/HashSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/HashSet.swift index 97b15dff..f7c37d21 100644 --- a/Sources/JavaKitCollection/generated/HashSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.HashSet", implements: JavaSet.self) open class HashSet: JavaObject { diff --git a/Sources/JavaKitCollection/generated/JavaCollection.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift similarity index 97% rename from Sources/JavaKitCollection/generated/JavaCollection.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift index 26ca6827..4d1c8a81 100644 --- a/Sources/JavaKitCollection/generated/JavaCollection.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Collection") public struct JavaCollection { diff --git a/Sources/JavaKitCollection/generated/JavaDictionary.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift similarity index 95% rename from Sources/JavaKitCollection/generated/JavaDictionary.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift index 43ded915..85ee9beb 100644 --- a/Sources/JavaKitCollection/generated/JavaDictionary.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.Dictionary") open class JavaDictionary: JavaObject { diff --git a/Sources/JavaKitCollection/generated/JavaIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift similarity index 89% rename from Sources/JavaKitCollection/generated/JavaIterator.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift index 382c44e5..1038a5e1 100644 --- a/Sources/JavaKitCollection/generated/JavaIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Iterator") public struct JavaIterator { diff --git a/Sources/JavaKitCollection/generated/JavaSet.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift similarity index 99% rename from Sources/JavaKitCollection/generated/JavaSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift index d924d2d1..7fec8e7a 100644 --- a/Sources/JavaKitCollection/generated/JavaSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Set", extends: JavaCollection.self) public struct JavaSet { diff --git a/Sources/JavaKitCollection/generated/LinkedList.swift b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift similarity index 99% rename from Sources/JavaKitCollection/generated/LinkedList.swift rename to Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift index 25abfbbf..9f27a3e1 100644 --- a/Sources/JavaKitCollection/generated/LinkedList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.LinkedList") public struct LinkedList { diff --git a/Sources/JavaKitCollection/generated/List.swift b/Sources/JavaStdlib/JavaUtil/generated/List.swift similarity index 99% rename from Sources/JavaKitCollection/generated/List.swift rename to Sources/JavaStdlib/JavaUtil/generated/List.swift index b264f88e..1d745ec3 100644 --- a/Sources/JavaKitCollection/generated/List.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/List.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.List") public struct List { diff --git a/Sources/JavaKitCollection/generated/ListIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift similarity index 94% rename from Sources/JavaKitCollection/generated/ListIterator.swift rename to Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift index 7b273160..a643efa7 100644 --- a/Sources/JavaKitCollection/generated/ListIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.ListIterator", extends: JavaIterator.self) public struct ListIterator { diff --git a/Sources/JavaKitCollection/generated/PriorityQueue.swift b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift similarity index 97% rename from Sources/JavaKitCollection/generated/PriorityQueue.swift rename to Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift index 14e50f31..13fc3629 100644 --- a/Sources/JavaKitCollection/generated/PriorityQueue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.PriorityQueue") open class PriorityQueue: JavaObject { diff --git a/Sources/JavaKitCollection/generated/Queue.swift b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift similarity index 97% rename from Sources/JavaKitCollection/generated/Queue.swift rename to Sources/JavaStdlib/JavaUtil/generated/Queue.swift index 44373fce..1d51d7c4 100644 --- a/Sources/JavaKitCollection/generated/Queue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Queue", extends: JavaCollection.self) public struct Queue { diff --git a/Sources/JavaKitCollection/generated/RandomAccess.swift b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift similarity index 79% rename from Sources/JavaKitCollection/generated/RandomAccess.swift rename to Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift index 0084ceed..e0fcb390 100644 --- a/Sources/JavaKitCollection/generated/RandomAccess.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.RandomAccess") public struct RandomAccess { diff --git a/Sources/JavaKitCollection/generated/Stack.swift b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift similarity index 93% rename from Sources/JavaKitCollection/generated/Stack.swift rename to Sources/JavaStdlib/JavaUtil/generated/Stack.swift index 867e9d44..636ed03b 100644 --- a/Sources/JavaKitCollection/generated/Stack.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.Stack") open class Stack: JavaObject { diff --git a/Sources/JavaKitCollection/generated/TreeMap.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift similarity index 98% rename from Sources/JavaKitCollection/generated/TreeMap.swift rename to Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift index 7796d555..4bf41ed3 100644 --- a/Sources/JavaKitCollection/generated/TreeMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.TreeMap") open class TreeMap: JavaObject { diff --git a/Sources/JavaKitCollection/generated/TreeSet.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift similarity index 97% rename from Sources/JavaKitCollection/generated/TreeSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift index 7e1807ab..cbfe8979 100644 --- a/Sources/JavaKitCollection/generated/TreeSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.TreeSet") open class TreeSet: JavaObject { diff --git a/Sources/JavaKitCollection/swift-java.config b/Sources/JavaStdlib/JavaUtil/swift-java.config similarity index 100% rename from Sources/JavaKitCollection/swift-java.config rename to Sources/JavaStdlib/JavaUtil/swift-java.config diff --git a/Sources/JavaKitFunction/generated/JavaBiConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift similarity index 91% rename from Sources/JavaKitFunction/generated/JavaBiConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift index c5f5b7bf..a8f33127 100644 --- a/Sources/JavaKitFunction/generated/JavaBiConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BiConsumer") public struct JavaBiConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift index edecbff5..d83e74a8 100644 --- a/Sources/JavaKitFunction/generated/JavaBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BiFunction") public struct JavaBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaBiPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift similarity index 94% rename from Sources/JavaKitFunction/generated/JavaBiPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift index 5fbc145e..f1870aa4 100644 --- a/Sources/JavaKitFunction/generated/JavaBiPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BiPredicate") public struct JavaBiPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift similarity index 93% rename from Sources/JavaKitFunction/generated/JavaBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift index 3d0b0cd5..a03e9add 100644 --- a/Sources/JavaKitFunction/generated/JavaBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface( "java.util.function.BinaryOperator", diff --git a/Sources/JavaKitFunction/generated/JavaBooleanSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift similarity index 85% rename from Sources/JavaKitFunction/generated/JavaBooleanSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift index eef96025..d6ce66de 100644 --- a/Sources/JavaKitFunction/generated/JavaBooleanSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BooleanSupplier") public struct JavaBooleanSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift similarity index 90% rename from Sources/JavaKitFunction/generated/JavaConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift index 91eac106..f8e64b2f 100644 --- a/Sources/JavaKitFunction/generated/JavaConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Consumer") public struct JavaConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaDoubleBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift index 0415ed90..ee82194d 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleBinaryOperator") public struct JavaDoubleBinaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift similarity index 89% rename from Sources/JavaKitFunction/generated/JavaDoubleConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift index 9c322584..a4a5a48f 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleConsumer") public struct JavaDoubleConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift index 4ff4f3ef..96879af4 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleFunction") public struct JavaDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaDoublePredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaDoublePredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift index b2add42f..27ac5861 100644 --- a/Sources/JavaKitFunction/generated/JavaDoublePredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoublePredicate") public struct JavaDoublePredicate { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift similarity index 85% rename from Sources/JavaKitFunction/generated/JavaDoubleSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift index 184a4353..807668bc 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleSupplier") public struct JavaDoubleSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaDoubleToIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift index ae4ab0b3..4de7ab7f 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleToIntFunction") public struct JavaDoubleToIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaDoubleToLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift index b0c9f9d6..492dbfa6 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleToLongFunction") public struct JavaDoubleToLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift similarity index 94% rename from Sources/JavaKitFunction/generated/JavaDoubleUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift index eb29139a..9db463ee 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleUnaryOperator") public struct JavaDoubleUnaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift similarity index 95% rename from Sources/JavaKitFunction/generated/JavaFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift index 744461a9..6e2361d7 100644 --- a/Sources/JavaKitFunction/generated/JavaFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Function") public struct JavaFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaIntBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift index 458885fe..f7a56f18 100644 --- a/Sources/JavaKitFunction/generated/JavaIntBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntBinaryOperator") public struct JavaIntBinaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaIntConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift index 704a4513..62130b88 100644 --- a/Sources/JavaKitFunction/generated/JavaIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntConsumer") public struct JavaIntConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift index cd7e7219..c3fcdf1e 100644 --- a/Sources/JavaKitFunction/generated/JavaIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntFunction") public struct JavaIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaIntPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift index 9580ef87..2ca8fcc0 100644 --- a/Sources/JavaKitFunction/generated/JavaIntPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntPredicate") public struct JavaIntPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaIntSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift similarity index 84% rename from Sources/JavaKitFunction/generated/JavaIntSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift index f0fae86a..05155e79 100644 --- a/Sources/JavaKitFunction/generated/JavaIntSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntSupplier") public struct JavaIntSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaIntToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaIntToDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift index 0a52e5b7..f3efcc6d 100644 --- a/Sources/JavaKitFunction/generated/JavaIntToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntToDoubleFunction") public struct JavaIntToDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaIntToLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift index 395dc876..1bbe989c 100644 --- a/Sources/JavaKitFunction/generated/JavaIntToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntToLongFunction") public struct JavaIntToLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift similarity index 93% rename from Sources/JavaKitFunction/generated/JavaIntUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift index 221b3a97..04ed0b2b 100644 --- a/Sources/JavaKitFunction/generated/JavaIntUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntUnaryOperator") public struct JavaIntUnaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaLongBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaLongBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift index 88035edc..1f3ca791 100644 --- a/Sources/JavaKitFunction/generated/JavaLongBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongBinaryOperator") public struct JavaLongBinaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaLongConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift index b0ef6013..82ae2a3b 100644 --- a/Sources/JavaKitFunction/generated/JavaLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongConsumer") public struct JavaLongConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift index 2d9a7559..eeed2bd8 100644 --- a/Sources/JavaKitFunction/generated/JavaLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongFunction") public struct JavaLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaLongPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaLongPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift index 3ed020eb..77f1293e 100644 --- a/Sources/JavaKitFunction/generated/JavaLongPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongPredicate") public struct JavaLongPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaLongSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift similarity index 84% rename from Sources/JavaKitFunction/generated/JavaLongSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift index 03ad6303..60e5421d 100644 --- a/Sources/JavaKitFunction/generated/JavaLongSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongSupplier") public struct JavaLongSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaLongToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaLongToDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift index 99b56ba3..a115477c 100644 --- a/Sources/JavaKitFunction/generated/JavaLongToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongToDoubleFunction") public struct JavaLongToDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaLongToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaLongToIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift index 2c952a17..984dbda3 100644 --- a/Sources/JavaKitFunction/generated/JavaLongToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongToIntFunction") public struct JavaLongToIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaLongUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift similarity index 93% rename from Sources/JavaKitFunction/generated/JavaLongUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift index fe99e338..7e379b0a 100644 --- a/Sources/JavaKitFunction/generated/JavaLongUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongUnaryOperator") public struct JavaLongUnaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaObjDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaObjDoubleConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift index cf1ac236..5c19ff03 100644 --- a/Sources/JavaKitFunction/generated/JavaObjDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ObjDoubleConsumer") public struct JavaObjDoubleConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaObjIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaObjIntConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift index 6c1e3b1e..a511db83 100644 --- a/Sources/JavaKitFunction/generated/JavaObjIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ObjIntConsumer") public struct JavaObjIntConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaObjLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaObjLongConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift index 5486e910..a2c32cfa 100644 --- a/Sources/JavaKitFunction/generated/JavaObjLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ObjLongConsumer") public struct JavaObjLongConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift similarity index 96% rename from Sources/JavaKitFunction/generated/JavaPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift index 9c953e3f..a2de359a 100644 --- a/Sources/JavaKitFunction/generated/JavaPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Predicate") public struct JavaPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift similarity index 85% rename from Sources/JavaKitFunction/generated/JavaSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift index b44740dd..a751ae79 100644 --- a/Sources/JavaKitFunction/generated/JavaSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Supplier") public struct JavaSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaToDoubleBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift similarity index 89% rename from Sources/JavaKitFunction/generated/JavaToDoubleBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift index 22ccf62c..739f1f17 100644 --- a/Sources/JavaKitFunction/generated/JavaToDoubleBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToDoubleBiFunction") public struct JavaToDoubleBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaToDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift index bf1c1d37..acdf7adb 100644 --- a/Sources/JavaKitFunction/generated/JavaToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToDoubleFunction") public struct JavaToDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToIntBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaToIntBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift index 6ebc0ff1..e1fdcc0a 100644 --- a/Sources/JavaKitFunction/generated/JavaToIntBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToIntBiFunction") public struct JavaToIntBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaToIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift index c960913d..4d963c57 100644 --- a/Sources/JavaKitFunction/generated/JavaToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToIntFunction") public struct JavaToIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToLongBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaToLongBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift index 93d0fc47..fb42c3e4 100644 --- a/Sources/JavaKitFunction/generated/JavaToLongBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToLongBiFunction") public struct JavaToLongBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaToLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift index f4fd5767..e34b5bd0 100644 --- a/Sources/JavaKitFunction/generated/JavaToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToLongFunction") public struct JavaToLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift similarity index 95% rename from Sources/JavaKitFunction/generated/JavaUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift index 232283ba..a31be86e 100644 --- a/Sources/JavaKitFunction/generated/JavaUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface( "java.util.function.UnaryOperator", extends: JavaFunction.self) diff --git a/Sources/JavaKitFunction/swift-java.config b/Sources/JavaStdlib/JavaUtilFunction/swift-java.config similarity index 100% rename from Sources/JavaKitFunction/swift-java.config rename to Sources/JavaStdlib/JavaUtilFunction/swift-java.config diff --git a/Sources/JavaKitJar/generated/Attributes.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift similarity index 98% rename from Sources/JavaKitJar/generated/Attributes.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift index f173c98d..710ce640 100644 --- a/Sources/JavaKitJar/generated/Attributes.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.util.jar.Attributes") open class Attributes: JavaObject { diff --git a/Sources/JavaKitJar/generated/JarEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarEntry.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift index 3a90d6a8..7140cb97 100644 --- a/Sources/JavaKitJar/generated/JarEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.JarEntry") open class JarEntry: ZipEntry { diff --git a/Sources/JavaKitJar/generated/JarFile.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarFile.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift index d5ea9969..db84e626 100644 --- a/Sources/JavaKitJar/generated/JarFile.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.util.jar.JarFile") open class JarFile: JavaObject { diff --git a/Sources/JavaKitJar/generated/JarInputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarInputStream.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift index f6d121d9..7d2ce423 100644 --- a/Sources/JavaKitJar/generated/JarInputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.JarInputStream") open class JarInputStream: JavaObject { diff --git a/Sources/JavaKitJar/generated/JarOutputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarOutputStream.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift index e13dc973..79a7c14e 100644 --- a/Sources/JavaKitJar/generated/JarOutputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.JarOutputStream") open class JarOutputStream: JavaObject { diff --git a/Sources/JavaKitJar/generated/Manifest.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift similarity index 95% rename from Sources/JavaKitJar/generated/Manifest.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift index 101af57c..b43b9e85 100644 --- a/Sources/JavaKitJar/generated/Manifest.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.Manifest") open class Manifest: JavaObject { diff --git a/Sources/JavaKitJar/generated/ZipEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift similarity index 99% rename from Sources/JavaKitJar/generated/ZipEntry.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift index 6a0fbd20..e276ae2a 100644 --- a/Sources/JavaKitJar/generated/ZipEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.zip.ZipEntry") open class ZipEntry: JavaObject { diff --git a/Sources/JavaKitJar/swift-java.config b/Sources/JavaStdlib/JavaUtilJar/swift-java.config similarity index 100% rename from Sources/JavaKitJar/swift-java.config rename to Sources/JavaStdlib/JavaUtilJar/swift-java.config diff --git a/Sources/JavaStdlib/README_PACKAGE_CONVENTION.md b/Sources/JavaStdlib/README_PACKAGE_CONVENTION.md new file mode 100644 index 00000000..ab65fa17 --- /dev/null +++ b/Sources/JavaStdlib/README_PACKAGE_CONVENTION.md @@ -0,0 +1,13 @@ +# Extracted Java Modules + +This directory contains Swift bindings for common Java standard library packages. +These pre-built bindings to solve a circular dependency problem - SwiftJava tools need these types to process and generate other bindings. + +You can also use these bindings directly in your SwiftJava programs to call Java classes without having to generate wrappers each time. + +The naming follows this pattern: Java package names become Swift target names. Example: `java.lang.util` becomes `JavaLangUtil`. + +Since Swift doesn't have namespaces like Java, all types appear at the top level in Swift. To avoid naming conflicts, +some types may be prefixed with 'J' (e.g. `JList` to avoid confusion with Swift native types). + +To see which Java types are included and any naming changes, check the `swift-java.config` file in each module. \ No newline at end of file diff --git a/Sources/JavaKit/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift similarity index 99% rename from Sources/JavaKit/AnyJavaObject.swift rename to Sources/SwiftJava/AnyJavaObject.swift index 5e0a88d0..02b9d70b 100644 --- a/Sources/JavaKit/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI /// Protocol that describes Swift types that are bridged to a Java class type. /// diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Array.swift b/Sources/SwiftJava/BridgedValues/JavaValue+Array.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+Array.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+Array.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Bool.swift b/Sources/SwiftJava/BridgedValues/JavaValue+Bool.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+Bool.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+Bool.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift b/Sources/SwiftJava/BridgedValues/JavaValue+FloatingPoint.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+FloatingPoint.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+Integers.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+String.swift b/Sources/SwiftJava/BridgedValues/JavaValue+String.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+String.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+String.swift diff --git a/Sources/JavaKit/Documentation.docc/JavaKit.md b/Sources/SwiftJava/Documentation.docc/JavaKit.md similarity index 99% rename from Sources/JavaKit/Documentation.docc/JavaKit.md rename to Sources/SwiftJava/Documentation.docc/JavaKit.md index fe0f50ba..a050e88b 100644 --- a/Sources/JavaKit/Documentation.docc/JavaKit.md +++ b/Sources/SwiftJava/Documentation.docc/JavaKit.md @@ -200,7 +200,7 @@ let primes = sieveClass.findPrimes(100) // returns a List? Putting it all together, we can define a main program in `Sources/JavaSieve/main.swift` that looks like this: ```swift -import JavaKit +import SwiftJNI let jvm = try JavaVirtualMachine.shared(classpath: ["QuadraticSieve-1.0.jar"]) do { diff --git a/Sources/JavaKit/Exceptions/Exception+Error.swift b/Sources/SwiftJava/Exceptions/Exception+Error.swift similarity index 100% rename from Sources/JavaKit/Exceptions/Exception+Error.swift rename to Sources/SwiftJava/Exceptions/Exception+Error.swift diff --git a/Sources/JavaKit/Exceptions/ExceptionHandling.swift b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift similarity index 100% rename from Sources/JavaKit/Exceptions/ExceptionHandling.swift rename to Sources/SwiftJava/Exceptions/ExceptionHandling.swift diff --git a/Sources/JavaKit/Exceptions/Throwable+Error.swift b/Sources/SwiftJava/Exceptions/Throwable+Error.swift similarity index 100% rename from Sources/JavaKit/Exceptions/Throwable+Error.swift rename to Sources/SwiftJava/Exceptions/Throwable+Error.swift diff --git a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift b/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift new file mode 100644 index 00000000..a67d225f --- /dev/null +++ b/Sources/SwiftJava/Helpers/_JNIMethodIDCache.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 +// +//===----------------------------------------------------------------------===// + +/// A cache used to hold references for JNI method and classes. +/// +/// This type is used internally in by the outputted JExtract wrappers +/// to improve performance of any JNI lookups. +public final class _JNIMethodIDCache: Sendable { + public struct Method: Hashable { + public let name: String + public let signature: String + + public init(name: String, signature: String) { + self.name = name + self.signature = signature + } + } + + nonisolated(unsafe) let _class: jclass? + nonisolated(unsafe) let methods: [Method: jmethodID] + + public var javaClass: jclass { + self._class! + } + + public init(environment: UnsafeMutablePointer!, className: String, methods: [Method]) { + guard let clazz = environment.interface.FindClass(environment, className) else { + fatalError("Class \(className) could not be found!") + } + self._class = environment.interface.NewGlobalRef(environment, clazz)! + self.methods = methods.reduce(into: [:]) { (result, method) in + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } + } + } + + + public subscript(_ method: Method) -> jmethodID? { + methods[method] + } + + public func cleanup(environment: UnsafeMutablePointer!) { + environment.interface.DeleteGlobalRef(environment, self._class) + } +} diff --git a/Sources/JavaKit/JavaClass+Initialization.swift b/Sources/SwiftJava/JavaClass+Initialization.swift similarity index 98% rename from Sources/JavaKit/JavaClass+Initialization.swift rename to Sources/SwiftJava/JavaClass+Initialization.swift index d443120f..8101c37a 100644 --- a/Sources/JavaKit/JavaClass+Initialization.swift +++ b/Sources/SwiftJava/JavaClass+Initialization.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI extension JavaClass { public typealias ObjectType = T diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/SwiftJava/JavaEnvironment.swift similarity index 97% rename from Sources/JavaKit/JavaEnvironment.swift rename to Sources/SwiftJava/JavaEnvironment.swift index 422262da..fd6a626b 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/SwiftJava/JavaEnvironment.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI #if canImport(Android) typealias JNINativeInterface_ = JNINativeInterface diff --git a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift similarity index 100% rename from Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift rename to Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift diff --git a/Sources/JavaKit/JavaKitVM/LockedState.swift b/Sources/SwiftJava/JavaKitVM/LockedState.swift similarity index 100% rename from Sources/JavaKit/JavaKitVM/LockedState.swift rename to Sources/SwiftJava/JavaKitVM/LockedState.swift diff --git a/Sources/JavaKit/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift similarity index 100% rename from Sources/JavaKit/JavaKitVM/ThreadLocalStorage.swift rename to Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift diff --git a/Sources/JavaKit/JavaObject+Inheritance.swift b/Sources/SwiftJava/JavaObject+Inheritance.swift similarity index 98% rename from Sources/JavaKit/JavaObject+Inheritance.swift rename to Sources/SwiftJava/JavaObject+Inheritance.swift index 43d86c2a..aa80fa54 100644 --- a/Sources/JavaKit/JavaObject+Inheritance.swift +++ b/Sources/SwiftJava/JavaObject+Inheritance.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI extension AnyJavaObject { /// Look up the other class type diff --git a/Sources/JavaKit/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift similarity index 99% rename from Sources/JavaKit/JavaObject+MethodCalls.swift rename to Sources/SwiftJava/JavaObject+MethodCalls.swift index 9f02a58d..1a62d0f4 100644 --- a/Sources/JavaKit/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI import JavaTypes /// Produce the mangling for a method with the given argument and result types. diff --git a/Sources/JavaKit/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift similarity index 98% rename from Sources/JavaKit/JavaObjectHolder.swift rename to Sources/SwiftJava/JavaObjectHolder.swift index 173991c9..50f60a82 100644 --- a/Sources/JavaKit/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object diff --git a/Sources/JavaKit/JavaRuntime+Reexport.swift b/Sources/SwiftJava/JavaRuntime+Reexport.swift similarity index 94% rename from Sources/JavaKit/JavaRuntime+Reexport.swift rename to Sources/SwiftJava/JavaRuntime+Reexport.swift index e65d4e18..f1a19e1c 100644 --- a/Sources/JavaKit/JavaRuntime+Reexport.swift +++ b/Sources/SwiftJava/JavaRuntime+Reexport.swift @@ -12,4 +12,4 @@ // //===----------------------------------------------------------------------===// -@_exported import JavaRuntime +@_exported import CJNI diff --git a/Sources/JavaKit/JavaValue.swift b/Sources/SwiftJava/JavaValue.swift similarity index 99% rename from Sources/JavaKit/JavaValue.swift rename to Sources/SwiftJava/JavaValue.swift index 0f208144..a740eb50 100644 --- a/Sources/JavaKit/JavaValue.swift +++ b/Sources/SwiftJava/JavaValue.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI import JavaTypes /// Describes a type that can be bridged with Java. diff --git a/Sources/JavaKit/Macros.swift b/Sources/SwiftJava/Macros.swift similarity index 90% rename from Sources/JavaKit/Macros.swift rename to Sources/SwiftJava/Macros.swift index 029344d8..8e1bb86c 100644 --- a/Sources/JavaKit/Macros.swift +++ b/Sources/SwiftJava/Macros.swift @@ -43,7 +43,7 @@ public macro JavaClass( _ fullClassName: String, extends: (any AnyJavaObject.Type)? = nil, implements: (any AnyJavaObject.Type)?... -) = #externalMacro(module: "JavaKitMacros", type: "JavaClassMacro") +) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro") /// Attached macro that declares that a particular `struct` type is a wrapper around a Java interface. /// @@ -74,7 +74,7 @@ public macro JavaClass( ) @attached(extension, conformances: AnyJavaObject) public macro JavaInterface(_ fullClassName: String, extends: (any AnyJavaObject.Type)?...) = - #externalMacro(module: "JavaKitMacros", type: "JavaClassMacro") + #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro") /// Attached macro that turns a Swift property into one that accesses a Java field on the underlying Java object. /// @@ -88,7 +88,7 @@ public macro JavaInterface(_ fullClassName: String, extends: (any AnyJavaObject. /// } /// ``` @attached(accessor) -public macro JavaField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "JavaKitMacros", type: "JavaFieldMacro") +public macro JavaField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "SwiftJavaMacros", type: "JavaFieldMacro") /// Attached macro that turns a Swift property into one that accesses a Java static field on the underlying Java object. @@ -102,7 +102,7 @@ public macro JavaField(_ javaFieldName: String? = nil, isFinal: Bool = false) = /// } /// ``` @attached(accessor) -public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "JavaKitMacros", type: "JavaFieldMacro") +public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "SwiftJavaMacros", type: "JavaFieldMacro") /// Attached macro that turns a Swift method into one that wraps a Java method on the underlying Java object. /// @@ -124,7 +124,7 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal /// /// corresponds to the Java constructor `HelloSwift(String name)`. @attached(body) -public macro JavaMethod() = #externalMacro(module: "JavaKitMacros", type: "JavaMethodMacro") +public macro JavaMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Attached macro that turns a Swift method on JavaClass into one that wraps /// a Java static method on the underlying Java class object. @@ -136,7 +136,7 @@ public macro JavaMethod() = #externalMacro(module: "JavaKitMacros", type: "JavaM /// func sayHelloBack(_ i: Int32) -> Double /// ``` @attached(body) -public macro JavaStaticMethod() = #externalMacro(module: "JavaKitMacros", type: "JavaMethodMacro") +public macro JavaStaticMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Macro that marks extensions to specify that all of the @JavaMethod /// methods are implementations of Java methods spelled as `native`. @@ -161,4 +161,4 @@ public macro JavaStaticMethod() = #externalMacro(module: "JavaKitMacros", type: /// } /// ``` @attached(peer) -public macro JavaImplementation(_ fullClassName: String) = #externalMacro(module: "JavaKitMacros", type: "JavaImplementationMacro") +public macro JavaImplementation(_ fullClassName: String) = #externalMacro(module: "SwiftJavaMacros", type: "JavaImplementationMacro") diff --git a/Sources/JavaKit/Optional+JavaObject.swift b/Sources/SwiftJava/Optional+JavaObject.swift similarity index 99% rename from Sources/JavaKit/Optional+JavaObject.swift rename to Sources/SwiftJava/Optional+JavaObject.swift index 7aff4294..f34ef2b3 100644 --- a/Sources/JavaKit/Optional+JavaObject.swift +++ b/Sources/SwiftJava/Optional+JavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI import JavaTypes extension Optional: JavaValue where Wrapped: AnyJavaObject { diff --git a/Sources/JavaKit/Optional+JavaOptional.swift b/Sources/SwiftJava/Optional+JavaOptional.swift similarity index 100% rename from Sources/JavaKit/Optional+JavaOptional.swift rename to Sources/SwiftJava/Optional+JavaOptional.swift diff --git a/Sources/JavaKit/generated/Appendable.swift b/Sources/SwiftJava/generated/Appendable.swift similarity index 95% rename from Sources/JavaKit/generated/Appendable.swift rename to Sources/SwiftJava/generated/Appendable.swift index 5c6663f2..7ff4f97e 100644 --- a/Sources/JavaKit/generated/Appendable.swift +++ b/Sources/SwiftJava/generated/Appendable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaInterface("java.lang.Appendable") public struct Appendable { diff --git a/Sources/JavaKit/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift similarity index 96% rename from Sources/JavaKit/generated/CharSequence.swift rename to Sources/SwiftJava/generated/CharSequence.swift index cab17273..c6e3d5be 100644 --- a/Sources/JavaKit/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaInterface("java.lang.CharSequence") public struct CharSequence { diff --git a/Sources/JavaKit/generated/Exception.swift b/Sources/SwiftJava/generated/Exception.swift similarity index 96% rename from Sources/JavaKit/generated/Exception.swift rename to Sources/SwiftJava/generated/Exception.swift index 6fc9de8c..8361f1b0 100644 --- a/Sources/JavaKit/generated/Exception.swift +++ b/Sources/SwiftJava/generated/Exception.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Exception") open class Exception: Throwable { diff --git a/Sources/JavaKit/generated/JavaArray.swift b/Sources/SwiftJava/generated/JavaArray.swift similarity index 99% rename from Sources/JavaKit/generated/JavaArray.swift rename to Sources/SwiftJava/generated/JavaArray.swift index 147f24df..65d3f88f 100644 --- a/Sources/JavaKit/generated/JavaArray.swift +++ b/Sources/SwiftJava/generated/JavaArray.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.reflect.Array") open class JavaArray: JavaObject { diff --git a/Sources/JavaKit/generated/JavaBoolean.swift b/Sources/SwiftJava/generated/JavaBoolean.swift similarity index 98% rename from Sources/JavaKit/generated/JavaBoolean.swift rename to Sources/SwiftJava/generated/JavaBoolean.swift index 0ab51f6d..995afc57 100644 --- a/Sources/JavaKit/generated/JavaBoolean.swift +++ b/Sources/SwiftJava/generated/JavaBoolean.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Boolean") open class JavaBoolean: JavaObject { diff --git a/Sources/JavaKit/generated/JavaByte.swift b/Sources/SwiftJava/generated/JavaByte.swift similarity index 99% rename from Sources/JavaKit/generated/JavaByte.swift rename to Sources/SwiftJava/generated/JavaByte.swift index e75c5996..a9ed639b 100644 --- a/Sources/JavaKit/generated/JavaByte.swift +++ b/Sources/SwiftJava/generated/JavaByte.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Byte") open class JavaByte: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift similarity index 99% rename from Sources/JavaKit/generated/JavaCharacter.swift rename to Sources/SwiftJava/generated/JavaCharacter.swift index eead7dfd..308a7fce 100644 --- a/Sources/JavaKit/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Character") open class JavaCharacter: JavaObject { diff --git a/Sources/JavaKit/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift similarity index 99% rename from Sources/JavaKit/generated/JavaClass.swift rename to Sources/SwiftJava/generated/JavaClass.swift index 03d7f5e6..1144482f 100644 --- a/Sources/JavaKit/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Class") open class JavaClass: JavaObject { diff --git a/Sources/JavaKit/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift similarity index 99% rename from Sources/JavaKit/generated/JavaClassLoader.swift rename to Sources/SwiftJava/generated/JavaClassLoader.swift index 6c877cc3..b6fabea4 100644 --- a/Sources/JavaKit/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.ClassLoader") open class JavaClassLoader: JavaObject { diff --git a/Sources/JavaKit/generated/JavaDouble.swift b/Sources/SwiftJava/generated/JavaDouble.swift similarity index 99% rename from Sources/JavaKit/generated/JavaDouble.swift rename to Sources/SwiftJava/generated/JavaDouble.swift index efa77a95..9d979fb3 100644 --- a/Sources/JavaKit/generated/JavaDouble.swift +++ b/Sources/SwiftJava/generated/JavaDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Double") open class JavaDouble: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaError.swift b/Sources/SwiftJava/generated/JavaError.swift similarity index 96% rename from Sources/JavaKit/generated/JavaError.swift rename to Sources/SwiftJava/generated/JavaError.swift index 97c2e555..da3c4218 100644 --- a/Sources/JavaKit/generated/JavaError.swift +++ b/Sources/SwiftJava/generated/JavaError.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Error") open class JavaError: Throwable { diff --git a/Sources/JavaKit/generated/JavaFloat.swift b/Sources/SwiftJava/generated/JavaFloat.swift similarity index 99% rename from Sources/JavaKit/generated/JavaFloat.swift rename to Sources/SwiftJava/generated/JavaFloat.swift index 0c38d1ae..9b11957e 100644 --- a/Sources/JavaKit/generated/JavaFloat.swift +++ b/Sources/SwiftJava/generated/JavaFloat.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Float") open class JavaFloat: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift similarity index 99% rename from Sources/JavaKit/generated/JavaInteger.swift rename to Sources/SwiftJava/generated/JavaInteger.swift index 646aac9e..286bc525 100644 --- a/Sources/JavaKit/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift similarity index 99% rename from Sources/JavaKit/generated/JavaLong.swift rename to Sources/SwiftJava/generated/JavaLong.swift index 7ff70efa..9e4a183e 100644 --- a/Sources/JavaKit/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Long") open class JavaLong: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaNumber.swift b/Sources/SwiftJava/generated/JavaNumber.swift similarity index 96% rename from Sources/JavaKit/generated/JavaNumber.swift rename to Sources/SwiftJava/generated/JavaNumber.swift index 414cd89b..81d0bacd 100644 --- a/Sources/JavaKit/generated/JavaNumber.swift +++ b/Sources/SwiftJava/generated/JavaNumber.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Number") open class JavaNumber: JavaObject { diff --git a/Sources/JavaKit/generated/JavaObject.swift b/Sources/SwiftJava/generated/JavaObject.swift similarity index 97% rename from Sources/JavaKit/generated/JavaObject.swift rename to Sources/SwiftJava/generated/JavaObject.swift index 07a6eaff..37e48b2d 100644 --- a/Sources/JavaKit/generated/JavaObject.swift +++ b/Sources/SwiftJava/generated/JavaObject.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Object") open class JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift similarity index 98% rename from Sources/JavaKit/generated/JavaOptional.swift rename to Sources/SwiftJava/generated/JavaOptional.swift index bd77cfed..7615c738 100644 --- a/Sources/JavaKit/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptionalDouble.swift b/Sources/SwiftJava/generated/JavaOptionalDouble.swift similarity index 98% rename from Sources/JavaKit/generated/JavaOptionalDouble.swift rename to Sources/SwiftJava/generated/JavaOptionalDouble.swift index 5926282a..8f1514be 100644 --- a/Sources/JavaKit/generated/JavaOptionalDouble.swift +++ b/Sources/SwiftJava/generated/JavaOptionalDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.OptionalDouble") open class JavaOptionalDouble: JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptionalInt.swift b/Sources/SwiftJava/generated/JavaOptionalInt.swift similarity index 97% rename from Sources/JavaKit/generated/JavaOptionalInt.swift rename to Sources/SwiftJava/generated/JavaOptionalInt.swift index 1237a085..9543d090 100644 --- a/Sources/JavaKit/generated/JavaOptionalInt.swift +++ b/Sources/SwiftJava/generated/JavaOptionalInt.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.OptionalInt") open class JavaOptionalInt: JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptionalLong.swift b/Sources/SwiftJava/generated/JavaOptionalLong.swift similarity index 97% rename from Sources/JavaKit/generated/JavaOptionalLong.swift rename to Sources/SwiftJava/generated/JavaOptionalLong.swift index 79a9e06f..ac9b4409 100644 --- a/Sources/JavaKit/generated/JavaOptionalLong.swift +++ b/Sources/SwiftJava/generated/JavaOptionalLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.OptionalLong") open class JavaOptionalLong: JavaObject { diff --git a/Sources/JavaKit/generated/JavaShort.swift b/Sources/SwiftJava/generated/JavaShort.swift similarity index 99% rename from Sources/JavaKit/generated/JavaShort.swift rename to Sources/SwiftJava/generated/JavaShort.swift index f425ae18..12ca1dd2 100644 --- a/Sources/JavaKit/generated/JavaShort.swift +++ b/Sources/SwiftJava/generated/JavaShort.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Short") open class JavaShort: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaString.swift b/Sources/SwiftJava/generated/JavaString.swift similarity index 99% rename from Sources/JavaKit/generated/JavaString.swift rename to Sources/SwiftJava/generated/JavaString.swift index c5f627f2..393c1acf 100644 --- a/Sources/JavaKit/generated/JavaString.swift +++ b/Sources/SwiftJava/generated/JavaString.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.String", implements: CharSequence.self) open class JavaString: JavaObject { diff --git a/Sources/JavaKit/generated/JavaVoid.swift b/Sources/SwiftJava/generated/JavaVoid.swift similarity index 92% rename from Sources/JavaKit/generated/JavaVoid.swift rename to Sources/SwiftJava/generated/JavaVoid.swift index 76a7334a..b2976196 100644 --- a/Sources/JavaKit/generated/JavaVoid.swift +++ b/Sources/SwiftJava/generated/JavaVoid.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Void") open class JavaVoid: JavaObject { diff --git a/Sources/JavaKit/generated/RuntimeException.swift b/Sources/SwiftJava/generated/RuntimeException.swift similarity index 96% rename from Sources/JavaKit/generated/RuntimeException.swift rename to Sources/SwiftJava/generated/RuntimeException.swift index c3e32506..aacd2362 100644 --- a/Sources/JavaKit/generated/RuntimeException.swift +++ b/Sources/SwiftJava/generated/RuntimeException.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.RuntimeException") open class RuntimeException: Exception { diff --git a/Sources/JavaKit/generated/Throwable.swift b/Sources/SwiftJava/generated/Throwable.swift similarity index 98% rename from Sources/JavaKit/generated/Throwable.swift rename to Sources/SwiftJava/generated/Throwable.swift index 574fedf5..823486d8 100644 --- a/Sources/JavaKit/generated/Throwable.swift +++ b/Sources/SwiftJava/generated/Throwable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Throwable") open class Throwable: JavaObject { diff --git a/Sources/JavaKit/swift-java.config b/Sources/SwiftJava/swift-java.config similarity index 100% rename from Sources/JavaKit/swift-java.config rename to Sources/SwiftJava/swift-java.config diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift similarity index 100% rename from Sources/JavaKitConfigurationShared/Configuration.swift rename to Sources/SwiftJavaConfigurationShared/Configuration.swift diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/SwiftJavaConfigurationShared/GenerationMode.swift similarity index 100% rename from Sources/JavaKitConfigurationShared/GenerationMode.swift rename to Sources/SwiftJavaConfigurationShared/GenerationMode.swift diff --git a/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift b/Sources/SwiftJavaConfigurationShared/GradleDependencyParsing.swift similarity index 100% rename from Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift rename to Sources/SwiftJavaConfigurationShared/GradleDependencyParsing.swift diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md index e8a3dfed..32a82eb4 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -72,7 +72,7 @@ OPTIONS: For example, the `JavaKitJar` library is generated with this command line: ```swift -swift-java wrap-java --swift-module JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config +swift-java wrap-java --swift-module JavaKitJar --depends-on SwiftJNI=Sources/SwiftJNI/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config ``` The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated. @@ -149,7 +149,7 @@ with an associated target that depends on `JavaKit`: Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined: ```swift -import JavaKit +import SwiftJNI @JavaImplementation("org.swift.javakit.HelloSwiftMain") struct HelloSwiftMain { @@ -178,9 +178,9 @@ The easiest way to build a command-line program in Swift is with the [Swift argu ```swift import ArgumentParser -import JavaKit +import SwiftJNI -@JavaClass("org.swift.javakit.HelloSwiftMain") +@JavaClass("org.swift.jni.HelloSwiftMain") struct HelloSwiftMain: ParsableCommand { @Option(name: .shortAndLong, help: "Enable verbose output") var verbose: Bool = false diff --git a/Sources/JavaKitMacros/GenerationMode.swift b/Sources/SwiftJavaMacros/GenerationMode.swift similarity index 100% rename from Sources/JavaKitMacros/GenerationMode.swift rename to Sources/SwiftJavaMacros/GenerationMode.swift diff --git a/Sources/JavaKitMacros/ImplementsJavaMacro.swift b/Sources/SwiftJavaMacros/ImplementsJavaMacro.swift similarity index 100% rename from Sources/JavaKitMacros/ImplementsJavaMacro.swift rename to Sources/SwiftJavaMacros/ImplementsJavaMacro.swift diff --git a/Sources/JavaKitMacros/JavaClassMacro.swift b/Sources/SwiftJavaMacros/JavaClassMacro.swift similarity index 100% rename from Sources/JavaKitMacros/JavaClassMacro.swift rename to Sources/SwiftJavaMacros/JavaClassMacro.swift diff --git a/Sources/JavaKitMacros/JavaFieldMacro.swift b/Sources/SwiftJavaMacros/JavaFieldMacro.swift similarity index 100% rename from Sources/JavaKitMacros/JavaFieldMacro.swift rename to Sources/SwiftJavaMacros/JavaFieldMacro.swift diff --git a/Sources/JavaKitMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift similarity index 100% rename from Sources/JavaKitMacros/JavaMethodMacro.swift rename to Sources/SwiftJavaMacros/JavaMethodMacro.swift diff --git a/Sources/JavaKitMacros/MacroErrors.swift b/Sources/SwiftJavaMacros/MacroErrors.swift similarity index 100% rename from Sources/JavaKitMacros/MacroErrors.swift rename to Sources/SwiftJavaMacros/MacroErrors.swift diff --git a/Sources/JavaKitMacros/JavaKitMacrosPlugin.swift b/Sources/SwiftJavaMacros/SwiftJNIMacrosPlugin.swift similarity index 93% rename from Sources/JavaKitMacros/JavaKitMacrosPlugin.swift rename to Sources/SwiftJavaMacros/SwiftJNIMacrosPlugin.swift index 255e61f5..637b2ea4 100644 --- a/Sources/JavaKitMacros/JavaKitMacrosPlugin.swift +++ b/Sources/SwiftJavaMacros/SwiftJNIMacrosPlugin.swift @@ -16,7 +16,7 @@ import SwiftCompilerPlugin import SwiftSyntaxMacros @main -struct JavaKitMacrosPlugin: CompilerPlugin { +struct SwiftJavaMacrosPlugin: CompilerPlugin { var providingMacros: [Macro.Type] = [ JavaImplementationMacro.self, JavaClassMacro.self, diff --git a/Sources/JavaKitMacros/SwiftSyntaxUtils.swift b/Sources/SwiftJavaMacros/SwiftSyntaxUtils.swift similarity index 100% rename from Sources/JavaKitMacros/SwiftSyntaxUtils.swift rename to Sources/SwiftJavaMacros/SwiftSyntaxUtils.swift diff --git a/Sources/JavaKitShared/TerminalColors.swift b/Sources/SwiftJavaShared/TerminalColors.swift similarity index 100% rename from Sources/JavaKitShared/TerminalColors.swift rename to Sources/SwiftJavaShared/TerminalColors.swift diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 2355ea81..8bafd257 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -14,16 +14,16 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork -import JavaKitReflection +import SwiftJava +import JavaUtilJar +import JavaNet +import JavaLangReflect import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared extension SwiftJava { struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index b32f34a0..29db6081 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -14,12 +14,12 @@ import Foundation import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import SwiftJavaLib +import SwiftJavaToolLib +import SwiftJava +import JavaUtilJar +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared /// Extract Java bindings from Swift sources or interface files. /// @@ -73,7 +73,7 @@ extension SwiftJava { @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, - e.g., Sources/JavaKitJar/swift-java.config. There should be one of these options + e.g., Sources/JavaJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. """ ) diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index c492e788..58c64690 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -14,13 +14,13 @@ import ArgumentParser import Foundation -import SwiftJavaLib -import JavaKit +import SwiftJavaToolLib +import SwiftJava import Foundation -import JavaKitJar -import SwiftJavaLib -import JavaKitConfigurationShared -import JavaKitShared +import JavaUtilJar +import SwiftJavaToolLib +import SwiftJavaConfigurationShared +import SwiftJavaShared import _Subprocess #if canImport(System) import System @@ -28,7 +28,7 @@ import System @preconcurrency import SystemPackage #endif -typealias Configuration = JavaKitConfigurationShared.Configuration +typealias Configuration = SwiftJavaConfigurationShared.Configuration extension SwiftJava { struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 3e4a62af..69ad3ebe 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -14,11 +14,11 @@ import Foundation import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import SwiftJavaLib -import JavaKitConfigurationShared +import SwiftJavaToolLib +import SwiftJava +import JavaUtilJar +import SwiftJavaToolLib +import SwiftJavaConfigurationShared extension SwiftJava { diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index e3ddfa44..c88ecbc8 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -14,14 +14,14 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork +import SwiftJava +import JavaUtilJar +import JavaNet import SwiftSyntax -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared // - MARK: Common Options diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index 41492d53..bd1ec3f2 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -import SwiftJavaLib -import JavaKitShared -import JavaRuntime -import JavaKit +import SwiftJavaToolLib +import SwiftJavaShared +import CJNI +import SwiftJava @JavaClass("java.lang.ClassLoader") public struct ClassLoader { diff --git a/Sources/SwiftJavaTool/String+Extensions.swift b/Sources/SwiftJavaTool/String+Extensions.swift index f2bb9e72..304e217d 100644 --- a/Sources/SwiftJavaTool/String+Extensions.swift +++ b/Sources/SwiftJavaTool/String+Extensions.swift @@ -14,11 +14,11 @@ import Foundation import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import SwiftJavaLib -import JavaKitConfigurationShared +import SwiftJavaToolLib +import SwiftJava +import JavaUtilJar +import SwiftJavaToolLib +import SwiftJavaConfigurationShared extension String { /// For a String that's of the form java.util.Vector, return the "Vector" diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift index 01917d4b..ac5182a0 100644 --- a/Sources/SwiftJavaTool/SwiftJava.swift +++ b/Sources/SwiftJavaTool/SwiftJava.swift @@ -14,16 +14,16 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork -import JavaKitReflection +import SwiftJava +import JavaUtilJar +import JavaNet +import JavaLangReflect import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared /// Command-line utility to drive the export of Java classes into Swift types. @main diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 20b177af..25b162e1 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -14,16 +14,16 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork -import JavaKitReflection +import SwiftJava +import JavaUtilJar +import JavaNet +import JavaLangReflect import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { var logLevel: Logger.Level { get set } diff --git a/Sources/SwiftJavaLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift similarity index 99% rename from Sources/SwiftJavaLib/JavaClassTranslator.swift rename to Sources/SwiftJavaToolLib/JavaClassTranslator.swift index ea6ca481..a7c4d76f 100644 --- a/Sources/SwiftJavaLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitReflection +import SwiftJava +import JavaLangReflect import SwiftSyntax /// Utility type that translates a single Java class into its corresponding diff --git a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift similarity index 97% rename from Sources/SwiftJavaLib/JavaTranslator+Configuration.swift rename to Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift index 0f764633..4fc9feb0 100644 --- a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension JavaTranslator { // /// Read a configuration file from the given URL. diff --git a/Sources/SwiftJavaLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift similarity index 100% rename from Sources/SwiftJavaLib/JavaTranslator+Validation.swift rename to Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift diff --git a/Sources/SwiftJavaLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift similarity index 98% rename from Sources/SwiftJavaLib/JavaTranslator.swift rename to Sources/SwiftJavaToolLib/JavaTranslator.swift index 225d3868..2f6f280c 100644 --- a/Sources/SwiftJavaLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -12,12 +12,12 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitReflection +import SwiftJava +import JavaLangReflect import JavaTypes import SwiftBasicFormat import SwiftSyntax -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import SwiftSyntaxBuilder /// Utility that translates Java classes into Swift source code to access @@ -45,7 +45,7 @@ package class JavaTranslator { /// an AnyJavaObject-conforming type) whereas the entry here should map to /// a value type. package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [ - "java.lang.String": ("String", "JavaKit"), + "java.lang.String": ("String", "SwiftJava"), ] /// The set of Swift modules that need to be imported to make the generated @@ -93,8 +93,8 @@ extension JavaTranslator { /// Default set of modules that will always be imported. private static let defaultImportedSwiftModules: Set = [ - "JavaKit", - "JavaRuntime", + "SwiftJava", + "CJNI", ] } diff --git a/Sources/SwiftJavaLib/MethodVariance.swift b/Sources/SwiftJavaToolLib/MethodVariance.swift similarity index 98% rename from Sources/SwiftJavaLib/MethodVariance.swift rename to Sources/SwiftJavaToolLib/MethodVariance.swift index 09bd6444..c130a75c 100644 --- a/Sources/SwiftJavaLib/MethodVariance.swift +++ b/Sources/SwiftJavaToolLib/MethodVariance.swift @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitReflection +import SwiftJava +import JavaLangReflect /// Captures the relationship between two methods by comparing their parameter /// and result types. diff --git a/Sources/SwiftJavaLib/OptionalKind.swift b/Sources/SwiftJavaToolLib/OptionalKind.swift similarity index 100% rename from Sources/SwiftJavaLib/OptionalKind.swift rename to Sources/SwiftJavaToolLib/OptionalKind.swift diff --git a/Sources/SwiftJavaLib/StringExtras.swift b/Sources/SwiftJavaToolLib/StringExtras.swift similarity index 100% rename from Sources/SwiftJavaLib/StringExtras.swift rename to Sources/SwiftJavaToolLib/StringExtras.swift diff --git a/Sources/SwiftJavaLib/TranslationError.swift b/Sources/SwiftJavaToolLib/TranslationError.swift similarity index 97% rename from Sources/SwiftJavaLib/TranslationError.swift rename to Sources/SwiftJavaToolLib/TranslationError.swift index d44fd2d7..1e01134b 100644 --- a/Sources/SwiftJavaLib/TranslationError.swift +++ b/Sources/SwiftJavaToolLib/TranslationError.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKitReflection +import JavaLangReflect /// Errors that can occur when translating Java types into Swift. enum TranslationError: Error { diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java index c3a9beb6..7b7bb1ec 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java @@ -16,6 +16,8 @@ import org.swift.swiftkit.core.SwiftInstanceCleanup; +import static org.swift.swiftkit.ffm.SwiftJavaLogGroup.LIFECYCLE; + import java.lang.foreign.MemorySegment; public class FFMSwiftInstanceCleanup implements SwiftInstanceCleanup { @@ -35,7 +37,7 @@ public void run() { // Allow null pointers just for AutoArena tests. if (type != null && memoryAddress != null) { - System.out.println("[debug] Destroy swift value [" + type.getSwiftName() + "]: " + memoryAddress); + SwiftRuntime.log(LIFECYCLE, "Destroy swift value [" + type.getSwiftName() + "]: " + memoryAddress); SwiftValueWitnessTable.destroy(type, memoryAddress); } } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index eca6be82..e03b9bfe 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -17,6 +17,7 @@ import org.swift.swiftkit.core.SwiftInstance; import org.swift.swiftkit.core.CallTraces; import org.swift.swiftkit.core.util.PlatformUtils; +import org.swift.swiftkit.ffm.SwiftRuntime.swiftjava; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; @@ -460,4 +461,30 @@ private static class swift_getTypeName { public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); } + public static void log(SwiftJavaLogGroup group, String message) { + if (group.isEnabled()) { + System.err.println(message); + } + } + + public static void log(SwiftJavaLogGroup group, String format, String ...args) { + if (group.isEnabled()) { + System.err.println(String.format(format, (Object[]) args)); + } + } + } + +enum SwiftJavaLogGroup { + LIFECYCLE; + + static boolean LOG_LIFECYCLE = + Boolean.getBoolean("swift-java.log.lifecycle"); + + boolean isEnabled() { + switch (this) { + case LIFECYCLE: return LOG_LIFECYCLE; + } + throw new IllegalArgumentException("Not handled log group: " + this); + } +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index 0b8ca1d3..06d1d555 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// @_spi(Testing) import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import SwiftSyntax import Testing diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 1f768fb2..66563fee 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -14,7 +14,7 @@ import JExtractSwiftLib import Testing -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import struct Foundation.CharacterSet enum RenderKind { diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index a87294b0..82747ec9 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class FuncCallbackImportTests { diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index a8d83a2a..ff19f4a2 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing @Suite diff --git a/Tests/JExtractSwiftTests/InternalExtractTests.swift b/Tests/JExtractSwiftTests/InternalExtractTests.swift index 78e71236..54829bd7 100644 --- a/Tests/JExtractSwiftTests/InternalExtractTests.swift +++ b/Tests/JExtractSwiftTests/InternalExtractTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class InternalExtractTests { diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index bfae447a..61a94062 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class JNIUnsignedNumberTests { diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 3ce839b9..6017c987 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing @Suite diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 3aa7d394..938b5e7f 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class MethodImportTests { diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 11b91e53..8109714c 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class UnsignedNumberTests { diff --git a/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift b/Tests/SwiftJavaConfigurationSharedTests/GradleDependencyParsingTests.swift similarity index 97% rename from Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift rename to Tests/SwiftJavaConfigurationSharedTests/GradleDependencyParsingTests.swift index dbd05f66..cb085fe2 100644 --- a/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift +++ b/Tests/SwiftJavaConfigurationSharedTests/GradleDependencyParsingTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing @Suite diff --git a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift similarity index 99% rename from Tests/JavaKitMacroTests/JavaClassMacroTests.swift rename to Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift index 1dc08ba8..7eead3d2 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKitMacros +import SwiftJavaMacros import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros diff --git a/Tests/JavaKitTests/BasicRuntimeTests.swift b/Tests/SwiftJavaTests/BasicRuntimeTests.swift similarity index 98% rename from Tests/JavaKitTests/BasicRuntimeTests.swift rename to Tests/SwiftJavaTests/BasicRuntimeTests.swift index d42fa4b1..b6c18bee 100644 --- a/Tests/JavaKitTests/BasicRuntimeTests.swift +++ b/Tests/SwiftJavaTests/BasicRuntimeTests.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitNetwork +import SwiftJava +import JavaNet import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 /// Handy reference to the JVM abstraction. diff --git a/Tests/SwiftJavaTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift similarity index 89% rename from Tests/SwiftJavaTests/Java2SwiftTests.swift rename to Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index e2b68a34..f251766a 100644 --- a/Tests/SwiftJavaTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -13,8 +13,8 @@ //===----------------------------------------------------------------------===// @_spi(Testing) -import JavaKit -import SwiftJavaLib +import SwiftJava +import SwiftJavaToolLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 /// Handy reference to the JVM abstraction. @@ -42,7 +42,7 @@ class Java2SwiftTests: XCTestCase { JavaObject.self, swiftTypeName: "MyJavaObject", expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Object") public struct MyJavaObject { @@ -67,7 +67,7 @@ class Java2SwiftTests: XCTestCase { "java.lang.Object": ("JavaObject", nil), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Class", extends: JavaObject.self) public struct MyJavaClass { @@ -85,7 +85,7 @@ class Java2SwiftTests: XCTestCase { JavaMonth.self, swiftTypeName: "Month", expectedChunks: [ - "import JavaKit", + "import SwiftJava", "enum MonthCases: Equatable", "case APRIL", "public var enumValue: MonthCases!", @@ -164,7 +164,7 @@ class Java2SwiftTests: XCTestCase { "java.lang.ProcessBuilder$Redirect": [JavaClass().as(JavaClass.self)!], ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaMethod public func redirectInput() -> ProcessBuilder.Redirect! @@ -204,7 +204,7 @@ class Java2SwiftTests: XCTestCase { "java.lang.ProcessBuilder$Redirect": [JavaClass().as(JavaClass.self)!], ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaMethod public func redirectInput() -> ProcessBuilder.PBRedirect! @@ -248,13 +248,13 @@ class Java2SwiftTests: XCTestCase { MyObjects.self, swiftTypeName: "MyJavaObjects", translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.util.function.Supplier" : ("MySupplier", "JavaKitFunction"), - "java.lang.String" : ("JavaString", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.util.function.Supplier" : ("MySupplier", "JavaUtilFunction"), + "java.lang.String" : ("JavaString", "SwiftJava"), ], expectedChunks: [ """ - import JavaKitFunction + import JavaUtilFunction """, """ @JavaClass("java.util.Objects", extends: JavaObject.self) @@ -274,7 +274,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaObject", asClass: true, expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Object") open class JavaObject { @@ -305,10 +305,10 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaString", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.String") open class JavaString: JavaObject { @@ -343,7 +343,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Month", asClass: true, expectedChunks: [ - "import JavaKit", + "import SwiftJava", "enum MonthCases: Equatable", "case APRIL", "public var enumValue: MonthCases!", @@ -380,12 +380,12 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.ClassLoader" : ("ClassLoader", "JavaKit"), - "java.net.URL" : ("URL", "JavaKitNetwork"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.ClassLoader" : ("ClassLoader", "SwiftJava"), + "java.net.URL" : ("URL", "JavaNet"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.net.URLClassLoader") open class URLClassLoader: ClassLoader { @@ -411,11 +411,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.net.URL" : ("URL", "JavaKitNetwork"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.net.URL" : ("URL", "JavaNet"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.net.URLClassLoader") open class URLClassLoader: JavaObject { @@ -440,12 +440,12 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaByte", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Number" : ("JavaNumber", "JavaKit"), - "java.lang.Byte" : ("JavaByte", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Number" : ("JavaNumber", "SwiftJava"), + "java.lang.Byte" : ("JavaByte", "SwiftJava"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Byte") open class JavaByte: JavaNumber { @@ -464,11 +464,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "MyJavaIntFunction", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), "java.util.function.IntFunction": ("MyJavaIntFunction", nil), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaInterface("java.util.function.IntFunction") public struct MyJavaIntFunction { @@ -487,14 +487,14 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Method", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Class" : ("JavaClass", "JavaKit"), - "java.lang.reflect.Executable": ("Executable", "JavaKitReflection"), - "java.lang.reflect.Method": ("Method", "JavaKitReflection"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaKitReflection"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Class" : ("JavaClass", "SwiftJava"), + "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), + "java.lang.reflect.Method": ("Method", "JavaLangReflect"), + "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), ], expectedChunks: [ - "import JavaKitReflection", + "import JavaLangReflect", """ @JavaClass("java.lang.reflect.Method") open class Method: Executable { @@ -521,14 +521,14 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Constructor", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Class" : ("JavaClass", "JavaKit"), - "java.lang.reflect.Executable": ("Executable", "JavaKitReflection"), - "java.lang.reflect.Method": ("Method", "JavaKitReflection"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaKitReflection"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Class" : ("JavaClass", "SwiftJava"), + "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), + "java.lang.reflect.Method": ("Method", "JavaLangReflect"), + "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), ], expectedChunks: [ - "import JavaKitReflection", + "import JavaLangReflect", """ @JavaClass("java.lang.reflect.Constructor") open class Constructor: Executable { @@ -555,13 +555,13 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "NIOByteBuffer", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Class" : ("JavaClass", "JavaKit"), - "java.nio.Buffer": ("NIOBuffer", "JavaKitNIO"), - "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaKitNIO"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Class" : ("JavaClass", "SwiftJava"), + "java.nio.Buffer": ("NIOBuffer", "JavaNio"), + "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaNio"), ], expectedChunks: [ - "import JavaKitNIO", + "import JavaNio", """ @JavaClass("java.nio.ByteBuffer") open class NIOByteBuffer: NIOBuffer { diff --git a/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift similarity index 98% rename from Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift rename to Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift index a203486a..220f1c61 100644 --- a/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import SwiftJavaLib +import SwiftJavaToolLib import XCTest final class JavaTranslatorValidationTests: XCTestCase { From 2268bb66cd7da3f2e5efbb52c8ca12d594692828 Mon Sep 17 00:00:00 2001 From: David Ko Date: Wed, 27 Aug 2025 09:12:26 -0400 Subject: [PATCH 137/178] Align Renaming Changes (#375) --- README.md | 12 +++--- .../{JavaKit.md => SwiftJava.md} | 38 +++++++++---------- .../Commands/ConfigureCommand.swift | 11 ++---- 3 files changed, 28 insertions(+), 33 deletions(-) rename Sources/SwiftJava/Documentation.docc/{JavaKit.md => SwiftJava.md} (90%) diff --git a/README.md b/README.md index 9f676905..c15db541 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ cd Samples/JavaKitSampleApp To run a simple example app showcasing the jextract (Java calling Swift) approach you can: ```bash -./gradlew Samples:SwiftKitSampleApp:run +./gradlew Samples:SwiftJavaExtractFFMSampleApp:run ``` This will also generate the necessary sources (by invoking jextract, extracting the `Sources/ExampleSwiftLibrary`) @@ -152,7 +152,7 @@ Please refer to the [Samples](Samples) directory for more sample apps which show You can run Swift [ordo-one/package-benchmark](https://github.com/ordo-one/package-benchmark) and OpenJDK [JMH](https://github.com/openjdk/jmh) benchmarks in this project. -Swift benchmarks are located under `Benchmarks/` and JMH benchmarks are currently part of the SwiftKit sample project: `Samples/SwiftKitSampleApp/src/jmh` because they depend on generated sources from the sample. +Swift benchmarks are located under `Benchmarks/` and JMH benchmarks are currently part of the SwiftKit sample project: `Samples/SwiftJavaExtractFFMSampleApp/src/jmh` because they depend on generated sources from the sample. ### Swift benchmarks @@ -168,8 +168,8 @@ swift package benchmark In order to run JMH benchmarks you can: ```bash -cd Samples/SwiftKitSampleApp -gradle jmh +cd Samples/SwiftJavaExtractFFMSampleApp +./gradlew jmh ``` Please read documentation of both performance testing tools and understand that results must be interpreted and not just taken at face value. Benchmarking is tricky and environment sensitive task, so please be careful when constructing and reading benchmarks and their results. If in doubt, please reach out on the forums. @@ -183,8 +183,8 @@ To view the rendered docc documentation you can use the docc preview command: ```bash xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc -# OR JavaKit to view JavaKit documentation: -# xcrun docc preview Sources/SwiftJNI/Documentation.docc +# OR SwiftJava to view SwiftJava documentation: +# xcrun docc preview Sources/SwiftJava/Documentation.docc # ======================================== # Starting Local Preview Server diff --git a/Sources/SwiftJava/Documentation.docc/JavaKit.md b/Sources/SwiftJava/Documentation.docc/SwiftJava.md similarity index 90% rename from Sources/SwiftJava/Documentation.docc/JavaKit.md rename to Sources/SwiftJava/Documentation.docc/SwiftJava.md index a050e88b..d4b5dacd 100644 --- a/Sources/SwiftJava/Documentation.docc/JavaKit.md +++ b/Sources/SwiftJava/Documentation.docc/SwiftJava.md @@ -1,8 +1,8 @@ -# JavaKit +# SwiftJava Library and tools to make it easy to use Java libraries from Swift using the Java Native Interface (JNI). -## JavaKit: Using Java libraries from Swift +## SwiftJava: Using Java libraries from Swift 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`: @@ -77,7 +77,7 @@ Swift ensures that the Java garbage collector will keep the object alive until ` ### Creating a Java Virtual Machine instance from Swift -When JavaKit requires a running Java Virtual Machine to use an operation (for example, to create an instance of `BigInteger`), it will query to determine if one is running and, if not, create one. To exercise more control over the creation and configuration of the Java virtual machine, use the `JavaVirtualMachine` class, which provides creation and query operations. One can create a shared instance by calling `JavaVirtualMachine.shared()`, optionally passing along extra options to the JVM (such as the class path): +When SwiftJava requires a running Java Virtual Machine to use an operation (for example, to create an instance of `BigInteger`), it will query to determine if one is running and, if not, create one. To exercise more control over the creation and configuration of the Java virtual machine, use the `JavaVirtualMachine` class, which provides creation and query operations. One can create a shared instance by calling `JavaVirtualMachine.shared()`, optionally passing along extra options to the JVM (such as the class path): ```swift let javaVirtualMachine = try JavaVirtualMachine.shared() @@ -100,7 +100,7 @@ let bigInt = BigInteger(veryBigNumber, environment: jniEnvironment) 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-java generate --module-name JavaSieve --jar QuadraticSieve-1.0.jar +swift-java configure --swift-module JavaSieve --jar QuadraticSieve-1.0.jar ``` The resulting configuration file will look something like this: @@ -138,7 +138,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 `swift-java` 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 (`SwiftJava`) 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: @@ -152,7 +152,7 @@ These warnings mean that some of the APIs in the Java library aren't available i .target( name: "JavaMath", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), ], plugins: [ .plugin(name: "SwiftJavaPlugin", package: "swift-java"), @@ -237,10 +237,10 @@ if let url = myObject.as(URL.self) { ### Implementing Java `native` methods in Swift -JavaKit supports implementing Java `native` methods in Swift using JNI with the `@JavaImplementation` macro. In Java, the method must be declared as `native`, e.g., +SwiftJava supports implementing Java `native` methods in Swift using JNI with the `@JavaImplementation` macro. In Java, the method must be declared as `native`, e.g., ```java -package org.swift.javakit.example; +package org.swift.swiftjava.example; public class HelloSwift { static { @@ -256,15 +256,15 @@ On the Swift side, the Java class needs to be exposed to Swift through `swift-ja ```swift { "classes" : { - "org.swift.javakit.example.HelloSwift" : "Hello", + "org.swift.swiftjava.example.HelloSwift" : "Hello", } } ``` -Implementations of `native` methods are written in an extension of the Swift type that has been marked with `@JavaImplementation`. The methods themselves must be marked with `@JavaMethod`, indicating that they are available to Java as well. To help ensure that the Swift code implements all of the `native` methods with the right signatures, JavaKit produces a protocol with the Swift type name suffixed by `NativeMethods`. Declare conformance to that protocol and implement its requirements, for example: +Implementations of `native` methods are written in an extension of the Swift type that has been marked with `@JavaImplementation`. The methods themselves must be marked with `@JavaMethod`, indicating that they are available to Java as well. To help ensure that the Swift code implements all of the `native` methods with the right signatures, SwiftJava produces a protocol with the Swift type name suffixed by `NativeMethods`. Declare conformance to that protocol and implement its requirements, for example: ```swift -@JavaImplementation("org.swift.javakit.HelloSwift") +@JavaImplementation("org.swift.swiftjava.HelloSwift") extension Hello: HelloNativeMethods { @JavaMethod func reportStatistics(_ meaning: String, _ numbers: [Double]) -> String { @@ -278,7 +278,7 @@ Java native methods that throw any checked exception should be marked as `throws The Swift implementations of Java `native` constructors and static methods require an additional Swift parameter `environment: JNIEnvironment? = nil`, which will receive the JNI environment in which the function is being executed. In case of nil, the `JavaVirtualMachine.shared().environment()` value will be used. -## JavaKit: Using Java libraries from Swift +## SwiftJava: Using Java libraries from Swift This section describes how Java libraries and mapped into Swift and their use from Swift. @@ -353,7 +353,7 @@ for entry in jarFile.entries()! { `JavaMethod` is a [function body macro](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0415-function-body-macros.md) that translates the argument and result types to/from Java and performs a call to the named method via JNI. -A Java method or constructor that throws a checked exception should be marked as `throws` in Swift. Swift's projection of Java throwable types (as `JavaKit.Throwable`) conforms to the Swift `Error` protocol, so Java exceptions will be rethrown as Swift errors. +A Java method or constructor that throws a checked exception should be marked as `throws` in Swift. Swift's projection of Java throwable types (as `SwiftJava.Throwable`) conforms to the Swift `Error` protocol, so Java exceptions will be rethrown as Swift errors. ### Java <-> Swift Type mapping @@ -380,14 +380,14 @@ For Swift projections of Java classes, the Swift type itself conforms to the `An Because Java has implicitly nullability of references, `AnyJavaObject` types do not directly conform to `JavaValue`: rather, optionals of `AnyJavaObject`-conforming type conform to `JavaValue`. This requires Swift code to deal with the optionality at interface boundaries rather than invite implicit NULL pointer dereferences. -A number of JavaKit modules provide Swift projections of Java classes and interfaces. Here are a few: +A number of SwiftJava modules provide Swift projections of Java classes and interfaces. Here are a few: | Java class | Swift class | Swift module | | --------------------- | -------------- | ---------------- | -| `java.lang.Object` | `JavaObject` | `JavaKit` | -| `java.lang.Class` | `JavaClass` | `JavaKit` | -| `java.lang.Throwable` | `Throwable` | `JavaKit` | -| `java.net.URL` | `URL` | `JavaKitNetwork` | +| `java.lang.Object` | `JavaObject` | `SwiftJava` | +| `java.lang.Class` | `JavaClass` | `SwiftJava` | +| `java.lang.Throwable` | `Throwable` | `SwiftJava` | +| `java.net.URL` | `URL` | `JavaNet` | 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 @@ -406,7 +406,7 @@ When building Java sources using the JavaCompilerPlugin this option is passed by ### Class objects and static methods -Every `AnyJavaObject` has a property `javaClass` that provides an instance of `JavaClass` specialized to the type. For example, `url.javaClass` will produce an instance of `JavaClass`. The `JavaClass` instance is a wrapper around a Java class object (`java.lang.Class`) that has two roles in Swift. First, it provides access to all of the APIs on the Java class object. The `JavaKitReflection` library, for example, exposes these APIs and the types they depend on (`Method`, +Every `AnyJavaObject` has a property `javaClass` that provides an instance of `JavaClass` specialized to the type. For example, `url.javaClass` will produce an instance of `JavaClass`. The `JavaClass` instance is a wrapper around a Java class object (`java.lang.Class`) that has two roles in Swift. First, it provides access to all of the APIs on the Java class object. The `JavaLangReflect` library, for example, exposes these APIs and the types they depend on (`Method`, `Constructor`, etc.) for dynamic reflection. Second, the `JavaClass` provides access to the `static` methods on the Java class. For example, [`java.net.URLConnection`](https://docs.oracle.com/javase/8/docs/api/java/net/URLConnection.html) has static methods to access default settings, such as the default for the `allowUserInteraction` field. These are exposed as instance methods on `JavaClass`, e.g., ```swift diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 8bafd257..3bce6c18 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -35,9 +35,7 @@ extension SwiftJava { @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." - ) + @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( @@ -57,9 +55,7 @@ extension SwiftJava { swiftModule } - @Argument( - help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file." - ) + @Argument(help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file.") var input: String? } } @@ -199,10 +195,9 @@ extension SwiftJava.ConfigureCommand { 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 +} From 3c36c767c6268d5b9d051e63ab669b5c91f1bb0d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 28 Aug 2025 16:45:32 +0900 Subject: [PATCH 138/178] rename CJNI module to avoid conflicts; call it CSwiftJavaJNI (#377) --- Benchmarks/Package.swift | 2 +- Package.swift | 8 ++++---- Samples/JavaDependencySampleApp/Package.swift | 2 +- Samples/SwiftJavaExtractFFMSampleApp/Package.swift | 2 +- .../Sources/MySwiftLibrary/jni/JNIImplementations.swift | 2 +- Samples/SwiftJavaExtractJNISampleApp/Package.swift | 2 +- Sources/CJNI/include/module.modulemap | 4 ---- Sources/{CJNI => CSwiftJavaJNI}/dummy.c | 0 .../CJNI.h => CSwiftJavaJNI/include/CSwiftJavaJNI.h} | 6 +++--- Sources/CSwiftJavaJNI/include/module.modulemap | 4 ++++ .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JavaKitDependencyResolver/DependencyResolver.swift | 2 +- .../JavaStdlib/JavaIO/generated/BufferedInputStream.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Charset.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Closeable.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/File.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/FileReader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Flushable.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/InputStream.swift | 2 +- .../JavaStdlib/JavaIO/generated/InputStreamReader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/OutputStream.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Path.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Readable.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Reader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/StringReader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/WatchService.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Writer.swift | 2 +- .../JavaLangReflect/generated/AccessibleObject.swift | 2 +- .../JavaLangReflect/generated/AnnotatedType.swift | 2 +- .../JavaStdlib/JavaLangReflect/generated/Annotation.swift | 2 +- .../JavaLangReflect/generated/Constructor.swift | 2 +- .../JavaStdlib/JavaLangReflect/generated/Executable.swift | 2 +- Sources/JavaStdlib/JavaLangReflect/generated/Field.swift | 2 +- .../JavaLangReflect/generated/GenericArrayType.swift | 2 +- .../JavaLangReflect/generated/GenericDeclaration.swift | 2 +- Sources/JavaStdlib/JavaLangReflect/generated/Method.swift | 2 +- .../JavaStdlib/JavaLangReflect/generated/Parameter.swift | 2 +- .../JavaLangReflect/generated/ParameterizedType.swift | 2 +- Sources/JavaStdlib/JavaLangReflect/generated/Type.swift | 2 +- .../JavaLangReflect/generated/TypeVariable.swift | 2 +- .../JavaLangReflect/generated/WildcardType.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URI.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URL.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URLConnection.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/BitSet.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/HashMap.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/HashSet.swift | 2 +- .../JavaStdlib/JavaUtil/generated/JavaCollection.swift | 2 +- .../JavaStdlib/JavaUtil/generated/JavaDictionary.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/List.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/Queue.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/Stack.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift | 2 +- .../JavaUtilFunction/generated/JavaBiConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaBiPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaBooleanSupplier.swift | 2 +- .../JavaUtilFunction/generated/JavaConsumer.swift | 2 +- .../generated/JavaDoubleBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaDoubleConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaDoubleFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaDoublePredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaDoubleSupplier.swift | 2 +- .../generated/JavaDoubleToIntFunction.swift | 2 +- .../generated/JavaDoubleToLongFunction.swift | 2 +- .../generated/JavaDoubleUnaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaFunction.swift | 2 +- .../generated/JavaIntBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaIntConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaIntFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaIntPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaIntSupplier.swift | 2 +- .../generated/JavaIntToDoubleFunction.swift | 2 +- .../generated/JavaIntToLongFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaIntUnaryOperator.swift | 2 +- .../generated/JavaLongBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaLongConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaLongFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaLongPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaLongSupplier.swift | 2 +- .../generated/JavaLongToDoubleFunction.swift | 2 +- .../generated/JavaLongToIntFunction.swift | 2 +- .../generated/JavaLongUnaryOperator.swift | 2 +- .../generated/JavaObjDoubleConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaObjIntConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaObjLongConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaSupplier.swift | 2 +- .../generated/JavaToDoubleBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToDoubleFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToIntBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToIntFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToLongBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToLongFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaUnaryOperator.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift | 2 +- .../JavaStdlib/JavaUtilJar/generated/JarInputStream.swift | 2 +- .../JavaUtilJar/generated/JarOutputStream.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift | 2 +- Sources/SwiftJava/AnyJavaObject.swift | 2 +- Sources/SwiftJava/JavaClass+Initialization.swift | 2 +- Sources/SwiftJava/JavaEnvironment.swift | 2 +- Sources/SwiftJava/JavaObject+Inheritance.swift | 2 +- Sources/SwiftJava/JavaObject+MethodCalls.swift | 2 +- Sources/SwiftJava/JavaObjectHolder.swift | 2 +- Sources/SwiftJava/JavaRuntime+Reexport.swift | 2 +- Sources/SwiftJava/JavaValue.swift | 2 +- Sources/SwiftJava/Optional+JavaObject.swift | 2 +- Sources/SwiftJava/generated/Appendable.swift | 2 +- Sources/SwiftJava/generated/CharSequence.swift | 2 +- Sources/SwiftJava/generated/Exception.swift | 2 +- Sources/SwiftJava/generated/JavaArray.swift | 2 +- Sources/SwiftJava/generated/JavaBoolean.swift | 2 +- Sources/SwiftJava/generated/JavaByte.swift | 2 +- Sources/SwiftJava/generated/JavaCharacter.swift | 2 +- Sources/SwiftJava/generated/JavaClass.swift | 2 +- Sources/SwiftJava/generated/JavaClassLoader.swift | 2 +- Sources/SwiftJava/generated/JavaDouble.swift | 2 +- Sources/SwiftJava/generated/JavaError.swift | 2 +- Sources/SwiftJava/generated/JavaFloat.swift | 2 +- Sources/SwiftJava/generated/JavaInteger.swift | 2 +- Sources/SwiftJava/generated/JavaLong.swift | 2 +- Sources/SwiftJava/generated/JavaNumber.swift | 2 +- Sources/SwiftJava/generated/JavaObject.swift | 2 +- Sources/SwiftJava/generated/JavaOptional.swift | 2 +- Sources/SwiftJava/generated/JavaOptionalDouble.swift | 2 +- Sources/SwiftJava/generated/JavaOptionalInt.swift | 2 +- Sources/SwiftJava/generated/JavaOptionalLong.swift | 2 +- Sources/SwiftJava/generated/JavaShort.swift | 2 +- Sources/SwiftJava/generated/JavaString.swift | 2 +- Sources/SwiftJava/generated/JavaVoid.swift | 2 +- Sources/SwiftJava/generated/RuntimeException.swift | 2 +- Sources/SwiftJava/generated/Throwable.swift | 2 +- Sources/SwiftJavaTool/Java/JavaClassLoader.swift | 2 +- Sources/SwiftJavaToolLib/JavaTranslator.swift | 2 +- 151 files changed, 157 insertions(+), 157 deletions(-) delete mode 100644 Sources/CJNI/include/module.modulemap rename Sources/{CJNI => CSwiftJavaJNI}/dummy.c (100%) rename Sources/{CJNI/include/CJNI.h => CSwiftJavaJNI/include/CSwiftJavaJNI.h} (86%) create mode 100644 Sources/CSwiftJavaJNI/include/module.modulemap diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index 955378fc..127e4d48 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -51,7 +51,7 @@ let package = Package( .executableTarget( name: "JavaApiCallBenchmarks", dependencies: [ - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "SwiftJava", package: "swift-java"), .product(name: "JavaNet", package: "swift-java"), .product(name: "Benchmark", package: "package-benchmark"), diff --git a/Package.swift b/Package.swift index a30b9510..1c0e9481 100644 --- a/Package.swift +++ b/Package.swift @@ -101,8 +101,8 @@ let package = Package( ), .library( - name: "CJNI", - targets: ["CJNI"] + name: "CSwiftJavaJNI", + targets: ["CSwiftJavaJNI"] ), .library( @@ -237,7 +237,7 @@ let package = Package( .target( name: "SwiftJava", dependencies: [ - "CJNI", + "CSwiftJavaJNI", "SwiftJavaMacros", "JavaTypes", "SwiftJavaConfigurationShared", // for Configuration reading at runtime @@ -359,7 +359,7 @@ let package = Package( ), .target( - name: "CJNI", + name: "CSwiftJavaJNI", swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index c5ae97c7..573a60cc 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -65,7 +65,7 @@ let package = Package( name: "JavaDependencySample", dependencies: [ .product(name: "SwiftJava", package: "swift-java"), - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "JavaUtilFunction", package: "swift-java"), "JavaCommonsCSV" ], diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift index 30630a01..b9765c24 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -64,7 +64,7 @@ let package = Package( name: "MySwiftLibrary", dependencies: [ .product(name: "SwiftJava", package: "swift-java"), - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift index 481265d5..50561d32 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("com.example.swift.HelloJava2Swift") open class HelloJava2Swift: JavaObject { diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index 0d2d2fa8..c7d5d717 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -61,7 +61,7 @@ let package = Package( name: "MySwiftLibrary", dependencies: [ .product(name: "SwiftJava", package: "swift-java"), - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Sources/CJNI/include/module.modulemap b/Sources/CJNI/include/module.modulemap deleted file mode 100644 index c71f30c2..00000000 --- a/Sources/CJNI/include/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CJNI { - umbrella header "CJNI.h" - export * -} diff --git a/Sources/CJNI/dummy.c b/Sources/CSwiftJavaJNI/dummy.c similarity index 100% rename from Sources/CJNI/dummy.c rename to Sources/CSwiftJavaJNI/dummy.c diff --git a/Sources/CJNI/include/CJNI.h b/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h similarity index 86% rename from Sources/CJNI/include/CJNI.h rename to Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h index bef6e7ff..fb5f71af 100644 --- a/Sources/CJNI/include/CJNI.h +++ b/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// -#ifndef Swift_CJNI_h -#define Swift_CJNI_h +#ifndef CSwiftJavaJNI_h +#define CSwiftJavaJNI_h #include -#endif /* Swift_CJNI_h */ +#endif /* CSwiftJavaJNI_h */ diff --git a/Sources/CSwiftJavaJNI/include/module.modulemap b/Sources/CSwiftJavaJNI/include/module.modulemap new file mode 100644 index 00000000..2be3eda9 --- /dev/null +++ b/Sources/CSwiftJavaJNI/include/module.modulemap @@ -0,0 +1,4 @@ +module CSwiftJavaJNI { + umbrella header "CSwiftJavaJNI.h" + export * +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 53679783..329b7dfd 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -409,7 +409,7 @@ extension JNISwift2JavaGenerator { // Generated by swift-java import SwiftJava - import CJNI + import CSwiftJavaJNI """ ) diff --git a/Sources/JavaKitDependencyResolver/DependencyResolver.swift b/Sources/JavaKitDependencyResolver/DependencyResolver.swift index 1ecc8dc5..2d339b15 100644 --- a/Sources/JavaKitDependencyResolver/DependencyResolver.swift +++ b/Sources/JavaKitDependencyResolver/DependencyResolver.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("org.swift.jni.dependencies.DependencyResolver") public struct DependencyResolver { diff --git a/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift index b4a13494..2a211eb9 100644 --- a/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.BufferedInputStream") open class BufferedInputStream: InputStream { diff --git a/Sources/JavaStdlib/JavaIO/generated/Charset.swift b/Sources/JavaStdlib/JavaIO/generated/Charset.swift index f3b9126c..03e85a8c 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Charset.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Charset.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.nio.charset.Charset") open class Charset: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/Closeable.swift b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift index 9e68c58b..1df52641 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Closeable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.io.Closeable") public struct Closeable { diff --git a/Sources/JavaStdlib/JavaIO/generated/File.swift b/Sources/JavaStdlib/JavaIO/generated/File.swift index 11831b5e..68b04efb 100644 --- a/Sources/JavaStdlib/JavaIO/generated/File.swift +++ b/Sources/JavaStdlib/JavaIO/generated/File.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.File") open class File: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift index 36f1686c..4c95b2b3 100644 --- a/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.FileDescriptor") open class FileDescriptor: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/FileReader.swift b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift index ed0898c7..b793a3f6 100644 --- a/Sources/JavaStdlib/JavaIO/generated/FileReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.FileReader") open class FileReader: InputStreamReader { diff --git a/Sources/JavaStdlib/JavaIO/generated/Flushable.swift b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift index 59854369..95cfc448 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Flushable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.io.Flushable") public struct Flushable { diff --git a/Sources/JavaStdlib/JavaIO/generated/InputStream.swift b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift index d521f265..b42a8fd8 100644 --- a/Sources/JavaStdlib/JavaIO/generated/InputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.InputStream", implements: Closeable.self) open class InputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift index faa04eda..7e55d633 100644 --- a/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.InputStreamReader") open class InputStreamReader: Reader { diff --git a/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift index 1d3ec356..d499508c 100644 --- a/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.OutputStream", implements: Closeable.self, Flushable.self) open class OutputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/Path.swift b/Sources/JavaStdlib/JavaIO/generated/Path.swift index 87956f10..235d9cef 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Path.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Path.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.nio.file.Path") public struct Path { diff --git a/Sources/JavaStdlib/JavaIO/generated/Readable.swift b/Sources/JavaStdlib/JavaIO/generated/Readable.swift index 25f48221..8961e18a 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Readable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Readable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.Readable") public struct Readable { diff --git a/Sources/JavaStdlib/JavaIO/generated/Reader.swift b/Sources/JavaStdlib/JavaIO/generated/Reader.swift index e133f741..5d8f77bf 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Reader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Reader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.Reader", implements: Readable.self, Closeable.self) open class Reader: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/StringReader.swift b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift index aa3efd38..ae4464ed 100644 --- a/Sources/JavaStdlib/JavaIO/generated/StringReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.StringReader") open class StringReader: Reader { diff --git a/Sources/JavaStdlib/JavaIO/generated/WatchService.swift b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift index 0b44c4fe..e2c570a3 100644 --- a/Sources/JavaStdlib/JavaIO/generated/WatchService.swift +++ b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.nio.file.WatchService", extends: Closeable.self) public struct WatchService { diff --git a/Sources/JavaStdlib/JavaIO/generated/Writer.swift b/Sources/JavaStdlib/JavaIO/generated/Writer.swift index 7712a1c0..fe20d27d 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Writer.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Writer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.Writer", implements: Appendable.self, Closeable.self, Flushable.self) open class Writer: JavaObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift index c118f78b..c6b0f4fc 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.AccessibleObject") open class AccessibleObject: JavaObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift index cc5b418c..87480ef4 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.AnnotatedType") public struct AnnotatedType { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift index 66ab49cc..1449bf32 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.annotation.Annotation") public struct Annotation { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift index 76f49f11..202cba9b 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Constructor") open class Constructor: Executable { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 31799784..3a6df8ea 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Executable", implements: GenericDeclaration.self) open class Executable: AccessibleObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift index 2c4a49e9..ba4f4538 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Field") open class Field: AccessibleObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift index 75ad2c75..42375e08 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.GenericArrayType", extends: Type.self) public struct GenericArrayType { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift index bec84aa5..839fa7db 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.GenericDeclaration") public struct GenericDeclaration { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift index 4489cf24..94371cd4 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Method") open class Method: Executable { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift index 6dea46fd..35ea7098 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Parameter") open class Parameter: JavaObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index a427a2a5..5e29ee05 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.ParameterizedType", extends: Type.self) public struct ParameterizedType { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index 05e8687e..ff52b41a 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.Type") public struct Type { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index e6a7899c..736fcfde 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) public struct TypeVariable { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift index 2514495a..a09b1b3b 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.WildcardType", extends: Type.self) public struct WildcardType { diff --git a/Sources/JavaStdlib/JavaNet/generated/URI.swift b/Sources/JavaStdlib/JavaNet/generated/URI.swift index f1a6c078..d6242406 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URI.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URI.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URI") open class URI: JavaObject { diff --git a/Sources/JavaStdlib/JavaNet/generated/URL.swift b/Sources/JavaStdlib/JavaNet/generated/URL.swift index 9140a1d9..95ac8fb4 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URL.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URL.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URL") open class URL: JavaObject { diff --git a/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift index 1750f197..d16e2eac 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URLClassLoader") open class URLClassLoader: JavaObject { diff --git a/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift index 6a93edd1..332e7425 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URLConnection") public struct URLConnection { diff --git a/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift index f0d257af..b8a1fb18 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.ArrayDeque") open class ArrayDeque: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift index b39ada56..9ead8f4c 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.ArrayList", implements: List.self, RandomAccess.self) open class ArrayList: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift index 2d89ba5a..7356b4b8 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.BitSet") open class BitSet: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift index 0229e16d..9c039b16 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Enumeration") public struct Enumeration { diff --git a/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift index 6fd38bc4..a217d48f 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.HashMap") open class HashMap: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift index f7c37d21..3b089c86 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.HashSet", implements: JavaSet.self) open class HashSet: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift index 4d1c8a81..3a8db21b 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Collection") public struct JavaCollection { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift index 85ee9beb..0a6508f5 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.Dictionary") open class JavaDictionary: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift index 1038a5e1..06f09d7f 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Iterator") public struct JavaIterator { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift index 7fec8e7a..12ff0056 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Set", extends: JavaCollection.self) public struct JavaSet { diff --git a/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift index 9f27a3e1..2464980f 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.LinkedList") public struct LinkedList { diff --git a/Sources/JavaStdlib/JavaUtil/generated/List.swift b/Sources/JavaStdlib/JavaUtil/generated/List.swift index 1d745ec3..2ebc1942 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/List.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/List.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.List") public struct List { diff --git a/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift index a643efa7..121330b1 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.ListIterator", extends: JavaIterator.self) public struct ListIterator { diff --git a/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift index 13fc3629..d9d9a85a 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.PriorityQueue") open class PriorityQueue: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/Queue.swift b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift index 1d51d7c4..b007e90a 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/Queue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Queue", extends: JavaCollection.self) public struct Queue { diff --git a/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift index e0fcb390..91b3fa31 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.RandomAccess") public struct RandomAccess { diff --git a/Sources/JavaStdlib/JavaUtil/generated/Stack.swift b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift index 636ed03b..be4330eb 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/Stack.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.Stack") open class Stack: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift index 4bf41ed3..c6dd3bb0 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.TreeMap") open class TreeMap: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift index cbfe8979..3ec9b1ea 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.TreeSet") open class TreeSet: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift index a8f33127..a01a0933 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BiConsumer") public struct JavaBiConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift index d83e74a8..ad54c58b 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BiFunction") public struct JavaBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift index f1870aa4..ee8aa0f5 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BiPredicate") public struct JavaBiPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift index a03e9add..1c7f5456 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface( "java.util.function.BinaryOperator", diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift index d6ce66de..e6e4a3ce 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BooleanSupplier") public struct JavaBooleanSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift index f8e64b2f..7da9bea8 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Consumer") public struct JavaConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift index ee82194d..f6c1c671 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleBinaryOperator") public struct JavaDoubleBinaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift index a4a5a48f..baf53c4a 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleConsumer") public struct JavaDoubleConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift index 96879af4..6f5d6752 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleFunction") public struct JavaDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift index 27ac5861..c594518d 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoublePredicate") public struct JavaDoublePredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift index 807668bc..839ae7e7 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleSupplier") public struct JavaDoubleSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift index 4de7ab7f..438249eb 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleToIntFunction") public struct JavaDoubleToIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift index 492dbfa6..76b916db 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleToLongFunction") public struct JavaDoubleToLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift index 9db463ee..cf1ff7e5 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleUnaryOperator") public struct JavaDoubleUnaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift index 6e2361d7..7a8165d8 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Function") public struct JavaFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift index f7a56f18..3df580c7 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntBinaryOperator") public struct JavaIntBinaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift index 62130b88..a8ac7c0b 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntConsumer") public struct JavaIntConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift index c3fcdf1e..6ebb7292 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntFunction") public struct JavaIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift index 2ca8fcc0..66c5e133 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntPredicate") public struct JavaIntPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift index 05155e79..0976fd53 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntSupplier") public struct JavaIntSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift index f3efcc6d..9891e815 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntToDoubleFunction") public struct JavaIntToDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift index 1bbe989c..17a7b7fa 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntToLongFunction") public struct JavaIntToLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift index 04ed0b2b..89528a2b 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntUnaryOperator") public struct JavaIntUnaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift index 1f3ca791..1cd53b93 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongBinaryOperator") public struct JavaLongBinaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift index 82ae2a3b..4c84754a 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongConsumer") public struct JavaLongConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift index eeed2bd8..9ce4cef1 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongFunction") public struct JavaLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift index 77f1293e..8f8f91fc 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongPredicate") public struct JavaLongPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift index 60e5421d..e0a203a1 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongSupplier") public struct JavaLongSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift index a115477c..7167d8fc 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongToDoubleFunction") public struct JavaLongToDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift index 984dbda3..00671304 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongToIntFunction") public struct JavaLongToIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift index 7e379b0a..8cf8944b 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongUnaryOperator") public struct JavaLongUnaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift index 5c19ff03..aa7d5a48 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ObjDoubleConsumer") public struct JavaObjDoubleConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift index a511db83..c53c3631 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ObjIntConsumer") public struct JavaObjIntConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift index a2c32cfa..ff4f7798 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ObjLongConsumer") public struct JavaObjLongConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift index a2de359a..b888e178 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Predicate") public struct JavaPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift index a751ae79..4d4c73cd 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Supplier") public struct JavaSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift index 739f1f17..35f77b78 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToDoubleBiFunction") public struct JavaToDoubleBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift index acdf7adb..56ee180d 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToDoubleFunction") public struct JavaToDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift index e1fdcc0a..dc17fa2b 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToIntBiFunction") public struct JavaToIntBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift index 4d963c57..3663f499 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToIntFunction") public struct JavaToIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift index fb42c3e4..1d5fc739 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToLongBiFunction") public struct JavaToLongBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift index e34b5bd0..66805be7 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToLongFunction") public struct JavaToLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift index a31be86e..11dc00ee 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface( "java.util.function.UnaryOperator", extends: JavaFunction.self) diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift index 710ce640..4897ebe3 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.Attributes") open class Attributes: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift index 7140cb97..adfb6d17 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarEntry") open class JarEntry: ZipEntry { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift index db84e626..e630afd2 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarFile") open class JarFile: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift index 7d2ce423..60f007fe 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarInputStream") open class JarInputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift index 79a7c14e..bd27471f 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarOutputStream") open class JarOutputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift index b43b9e85..2033f41a 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.Manifest") open class Manifest: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift index e276ae2a..3066d54c 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.zip.ZipEntry") open class ZipEntry: JavaObject { diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index 02b9d70b..e514d3e6 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI /// Protocol that describes Swift types that are bridged to a Java class type. /// diff --git a/Sources/SwiftJava/JavaClass+Initialization.swift b/Sources/SwiftJava/JavaClass+Initialization.swift index 8101c37a..89bfa62b 100644 --- a/Sources/SwiftJava/JavaClass+Initialization.swift +++ b/Sources/SwiftJava/JavaClass+Initialization.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI extension JavaClass { public typealias ObjectType = T diff --git a/Sources/SwiftJava/JavaEnvironment.swift b/Sources/SwiftJava/JavaEnvironment.swift index fd6a626b..a533a932 100644 --- a/Sources/SwiftJava/JavaEnvironment.swift +++ b/Sources/SwiftJava/JavaEnvironment.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI #if canImport(Android) typealias JNINativeInterface_ = JNINativeInterface diff --git a/Sources/SwiftJava/JavaObject+Inheritance.swift b/Sources/SwiftJava/JavaObject+Inheritance.swift index aa80fa54..f306b9c6 100644 --- a/Sources/SwiftJava/JavaObject+Inheritance.swift +++ b/Sources/SwiftJava/JavaObject+Inheritance.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI extension AnyJavaObject { /// Look up the other class type diff --git a/Sources/SwiftJava/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift index 1a62d0f4..4880f750 100644 --- a/Sources/SwiftJava/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI import JavaTypes /// Produce the mangling for a method with the given argument and result types. diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 50f60a82..319a09e8 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object diff --git a/Sources/SwiftJava/JavaRuntime+Reexport.swift b/Sources/SwiftJava/JavaRuntime+Reexport.swift index f1a19e1c..9b2c02a8 100644 --- a/Sources/SwiftJava/JavaRuntime+Reexport.swift +++ b/Sources/SwiftJava/JavaRuntime+Reexport.swift @@ -12,4 +12,4 @@ // //===----------------------------------------------------------------------===// -@_exported import CJNI +@_exported import CSwiftJavaJNI diff --git a/Sources/SwiftJava/JavaValue.swift b/Sources/SwiftJava/JavaValue.swift index a740eb50..46efdb3f 100644 --- a/Sources/SwiftJava/JavaValue.swift +++ b/Sources/SwiftJava/JavaValue.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI import JavaTypes /// Describes a type that can be bridged with Java. diff --git a/Sources/SwiftJava/Optional+JavaObject.swift b/Sources/SwiftJava/Optional+JavaObject.swift index f34ef2b3..46fd9970 100644 --- a/Sources/SwiftJava/Optional+JavaObject.swift +++ b/Sources/SwiftJava/Optional+JavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI import JavaTypes extension Optional: JavaValue where Wrapped: AnyJavaObject { diff --git a/Sources/SwiftJava/generated/Appendable.swift b/Sources/SwiftJava/generated/Appendable.swift index 7ff4f97e..b0c67ec5 100644 --- a/Sources/SwiftJava/generated/Appendable.swift +++ b/Sources/SwiftJava/generated/Appendable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.Appendable") public struct Appendable { diff --git a/Sources/SwiftJava/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift index c6e3d5be..eadc509e 100644 --- a/Sources/SwiftJava/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.CharSequence") public struct CharSequence { diff --git a/Sources/SwiftJava/generated/Exception.swift b/Sources/SwiftJava/generated/Exception.swift index 8361f1b0..e87684cb 100644 --- a/Sources/SwiftJava/generated/Exception.swift +++ b/Sources/SwiftJava/generated/Exception.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Exception") open class Exception: Throwable { diff --git a/Sources/SwiftJava/generated/JavaArray.swift b/Sources/SwiftJava/generated/JavaArray.swift index 65d3f88f..ae182208 100644 --- a/Sources/SwiftJava/generated/JavaArray.swift +++ b/Sources/SwiftJava/generated/JavaArray.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Array") open class JavaArray: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaBoolean.swift b/Sources/SwiftJava/generated/JavaBoolean.swift index 995afc57..bdf21df9 100644 --- a/Sources/SwiftJava/generated/JavaBoolean.swift +++ b/Sources/SwiftJava/generated/JavaBoolean.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Boolean") open class JavaBoolean: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaByte.swift b/Sources/SwiftJava/generated/JavaByte.swift index a9ed639b..e3f67c78 100644 --- a/Sources/SwiftJava/generated/JavaByte.swift +++ b/Sources/SwiftJava/generated/JavaByte.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Byte") open class JavaByte: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift index 308a7fce..406b45ee 100644 --- a/Sources/SwiftJava/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Character") open class JavaCharacter: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 1144482f..0f1af1cd 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Class") open class JavaClass: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift index b6fabea4..349cba8d 100644 --- a/Sources/SwiftJava/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.ClassLoader") open class JavaClassLoader: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaDouble.swift b/Sources/SwiftJava/generated/JavaDouble.swift index 9d979fb3..8d54f8de 100644 --- a/Sources/SwiftJava/generated/JavaDouble.swift +++ b/Sources/SwiftJava/generated/JavaDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Double") open class JavaDouble: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaError.swift b/Sources/SwiftJava/generated/JavaError.swift index da3c4218..4ba9d2ca 100644 --- a/Sources/SwiftJava/generated/JavaError.swift +++ b/Sources/SwiftJava/generated/JavaError.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Error") open class JavaError: Throwable { diff --git a/Sources/SwiftJava/generated/JavaFloat.swift b/Sources/SwiftJava/generated/JavaFloat.swift index 9b11957e..ac989531 100644 --- a/Sources/SwiftJava/generated/JavaFloat.swift +++ b/Sources/SwiftJava/generated/JavaFloat.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Float") open class JavaFloat: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift index 286bc525..94800037 100644 --- a/Sources/SwiftJava/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift index 9e4a183e..a986e9ef 100644 --- a/Sources/SwiftJava/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Long") open class JavaLong: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaNumber.swift b/Sources/SwiftJava/generated/JavaNumber.swift index 81d0bacd..78f988f1 100644 --- a/Sources/SwiftJava/generated/JavaNumber.swift +++ b/Sources/SwiftJava/generated/JavaNumber.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Number") open class JavaNumber: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaObject.swift b/Sources/SwiftJava/generated/JavaObject.swift index 37e48b2d..7db8a965 100644 --- a/Sources/SwiftJava/generated/JavaObject.swift +++ b/Sources/SwiftJava/generated/JavaObject.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Object") open class JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 7615c738..08cc764a 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptionalDouble.swift b/Sources/SwiftJava/generated/JavaOptionalDouble.swift index 8f1514be..0d0e2eae 100644 --- a/Sources/SwiftJava/generated/JavaOptionalDouble.swift +++ b/Sources/SwiftJava/generated/JavaOptionalDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.OptionalDouble") open class JavaOptionalDouble: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptionalInt.swift b/Sources/SwiftJava/generated/JavaOptionalInt.swift index 9543d090..2270e66e 100644 --- a/Sources/SwiftJava/generated/JavaOptionalInt.swift +++ b/Sources/SwiftJava/generated/JavaOptionalInt.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.OptionalInt") open class JavaOptionalInt: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptionalLong.swift b/Sources/SwiftJava/generated/JavaOptionalLong.swift index ac9b4409..10c3fbd0 100644 --- a/Sources/SwiftJava/generated/JavaOptionalLong.swift +++ b/Sources/SwiftJava/generated/JavaOptionalLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.OptionalLong") open class JavaOptionalLong: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaShort.swift b/Sources/SwiftJava/generated/JavaShort.swift index 12ca1dd2..4f387b36 100644 --- a/Sources/SwiftJava/generated/JavaShort.swift +++ b/Sources/SwiftJava/generated/JavaShort.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Short") open class JavaShort: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaString.swift b/Sources/SwiftJava/generated/JavaString.swift index 393c1acf..f3372f65 100644 --- a/Sources/SwiftJava/generated/JavaString.swift +++ b/Sources/SwiftJava/generated/JavaString.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.String", implements: CharSequence.self) open class JavaString: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaVoid.swift b/Sources/SwiftJava/generated/JavaVoid.swift index b2976196..54decbbc 100644 --- a/Sources/SwiftJava/generated/JavaVoid.swift +++ b/Sources/SwiftJava/generated/JavaVoid.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Void") open class JavaVoid: JavaObject { diff --git a/Sources/SwiftJava/generated/RuntimeException.swift b/Sources/SwiftJava/generated/RuntimeException.swift index aacd2362..14516ed1 100644 --- a/Sources/SwiftJava/generated/RuntimeException.swift +++ b/Sources/SwiftJava/generated/RuntimeException.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.RuntimeException") open class RuntimeException: Exception { diff --git a/Sources/SwiftJava/generated/Throwable.swift b/Sources/SwiftJava/generated/Throwable.swift index 823486d8..7df74b7e 100644 --- a/Sources/SwiftJava/generated/Throwable.swift +++ b/Sources/SwiftJava/generated/Throwable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Throwable") open class Throwable: JavaObject { diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index bd1ec3f2..d465a206 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -14,7 +14,7 @@ import SwiftJavaToolLib import SwiftJavaShared -import CJNI +import CSwiftJavaJNI import SwiftJava @JavaClass("java.lang.ClassLoader") diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 2f6f280c..7cb8a7ee 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -94,7 +94,7 @@ extension JavaTranslator { /// Default set of modules that will always be imported. private static let defaultImportedSwiftModules: Set = [ "SwiftJava", - "CJNI", + "CSwiftJavaJNI", ] } From 5744562b36b8c893448c5983742465c8e1a534f4 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 28 Aug 2025 16:57:44 +0900 Subject: [PATCH 139/178] Avoid jextracting +SwiftJava files (#378) --- Sources/JExtractSwiftLib/Swift2Java.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index e6271993..19241592 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -124,8 +124,15 @@ public struct SwiftToJava { } func canExtract(from file: URL) -> Bool { - file.lastPathComponent.hasSuffix(".swift") || - file.lastPathComponent.hasSuffix(".swiftinterface") + guard file.lastPathComponent.hasSuffix(".swift") || + file.lastPathComponent.hasSuffix(".swiftinterface") else { + return false + } + if file.lastPathComponent.hasSuffix("+SwiftJava.swift") { + return false + } + + return true } } From 73d7882fc69ab0ef9d2067c1544983a0a286367a Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 29 Aug 2025 08:30:40 +0200 Subject: [PATCH 140/178] [JExtract/JNI] Add support for protocols as parameter types (#376) --- .../MySwiftLibrary/ConcreteProtocolAB.swift | 28 ++ .../Sources/MySwiftLibrary/ProtocolA.swift | 24 ++ .../Sources/MySwiftLibrary/ProtocolB.swift | 29 +++ .../java/com/example/swift/EnumBenchmark.java | 0 .../com/example/swift/AlignmentEnumTest.java | 1 + .../java/com/example/swift/ProtocolTest.java | 75 ++++++ .../Convenience/SwiftSyntax+Extensions.swift | 6 +- .../FFM/CDeclLowering/CRepresentation.swift | 2 +- ...Swift2JavaGenerator+FunctionLowering.swift | 9 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 7 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 5 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 6 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 138 ++++++++-- ...ISwift2JavaGenerator+JavaTranslation.swift | 157 +++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 160 ++++++++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 33 +++ .../JNI/JNISwift2JavaGenerator.swift | 2 + Sources/JExtractSwiftLib/JavaParameter.swift | 45 +++- .../Swift2JavaTranslator.swift | 8 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 10 +- .../SwiftNominalTypeDeclaration.swift | 4 + ...ype.swift => SwiftType+GenericTypes.swift} | 32 +++ .../SwiftTypes/SwiftType.swift | 20 +- .../Documentation.docc/SupportedFeatures.md | 56 +++- .../swift/swiftkit/core/JNISwiftInstance.java | 33 +-- .../swift/swiftkit/core/SwiftInstance.java | 20 +- .../org/swift/swiftkit/AutoArenaTest.java | 25 +- .../swift/swiftkit/ffm/FFMSwiftInstance.java | 18 +- .../org/swift/swiftkit/ffm/AutoArenaTest.java | 1 + .../JNI/JNIClassTests.swift | 25 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 21 +- .../JNI/JNIModuleTests.swift | 1 + .../JNI/JNIOptionalTests.swift | 10 +- .../JNI/JNIProtocolTests.swift | 246 ++++++++++++++++++ .../JNI/JNIStructTests.swift | 14 +- Tests/JExtractSwiftTests/SendableTests.swift | 4 +- 36 files changed, 1121 insertions(+), 154 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/jmh/java/com/example/swift/EnumBenchmark.java (100%) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java rename Sources/JExtractSwiftLib/SwiftTypes/{SwiftType+RepresentativeConcreteeType.swift => SwiftType+GenericTypes.swift} (72%) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift new file mode 100644 index 00000000..ed55d039 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public class ConcreteProtocolAB: ProtocolA, ProtocolB { + public let constantA: Int64 + public let constantB: Int64 + public var mutable: Int64 = 0 + + public func name() -> String { + return "ConcreteProtocolAB" + } + + public init(constantA: Int64, constantB: Int64) { + self.constantA = constantA + self.constantB = constantB + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift new file mode 100644 index 00000000..d5281b81 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public protocol ProtocolA { + var constantA: Int64 { get } + var mutable: Int64 { get set } + + func name() -> String +} + +public func takeProtocol(_ proto1: any ProtocolA, _ proto2: some ProtocolA) -> Int64 { + return proto1.constantA + proto2.constantA +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift new file mode 100644 index 00000000..70d075c2 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.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 +// +//===----------------------------------------------------------------------===// + +public protocol ProtocolB { + var constantB: Int64 { get } +} + +public func takeCombinedProtocol(_ proto: some ProtocolA & ProtocolB) -> Int64 { + return proto.constantA + proto.constantB +} + +public func takeGenericProtocol(_ proto1: First, _ proto2: Second) -> Int64 { + return proto1.constantA + proto2.constantB +} + +public func takeCombinedGenericProtocol(_ proto: T) -> Int64 { + return proto.constantA + proto.constantB +} diff --git a/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java rename to Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java index 6be85c75..5bf05951 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.JNISwiftInstance; import org.swift.swiftkit.core.SwiftArena; import java.util.Optional; diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java new file mode 100644 index 00000000..c095a42a --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProtocolTest { + @Test + void takeProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + ConcreteProtocolAB proto2 = ConcreteProtocolAB.init(20, 1, arena); + assertEquals(30, MySwiftLibrary.takeProtocol(proto1, proto2)); + } + } + + @Test + void takeCombinedProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(15, MySwiftLibrary.takeCombinedProtocol(proto1)); + } + } + + @Test + void takeGenericProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + ConcreteProtocolAB proto2 = ConcreteProtocolAB.init(20, 1, arena); + assertEquals(11, MySwiftLibrary.takeGenericProtocol(proto1, proto2)); + } + } + + @Test + void takeCombinedGenericProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(15, MySwiftLibrary.takeCombinedGenericProtocol(proto1)); + } + } + + @Test + void protocolVariables() { + try (var arena = SwiftArena.ofConfined()) { + ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(10, proto1.getConstantA()); + assertEquals(0, proto1.getMutable()); + proto1.setMutable(3); + assertEquals(3, proto1.getMutable()); + } + } + + @Test + void protocolMethod() { + try (var arena = SwiftArena.ofConfined()) { + ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals("ConcreteProtocolAB", proto1.name()); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index 3719d99d..8a58f42a 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -95,7 +95,11 @@ extension DeclModifierSyntax { } extension WithModifiersSyntax { - var isPublic: Bool { + func isPublic(in type: NominalTypeDeclSyntaxNode?) -> Bool { + if let type, case .protocolDecl(let protocolDecl) = Syntax(type).as(SyntaxEnum.self) { + return protocolDecl.isPublic(in: nil) + } + return self.modifiers.contains { modifier in modifier.isPublic } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index d2a71def..193b545f 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 0a61708e..5dfa5b71 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -344,6 +344,9 @@ struct CdeclLowering { case .optional(let wrapped): return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) + + case .composite: + throw LoweringError.unhandledType(type) } } @@ -412,7 +415,7 @@ struct CdeclLowering { } throw LoweringError.unhandledType(.optional(wrappedType)) - case .function, .metatype, .optional: + case .function, .metatype, .optional, .composite: throw LoweringError.unhandledType(.optional(wrappedType)) } } @@ -513,7 +516,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -667,7 +670,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .genericParameter, .function, .optional, .existential, .opaque: + case .genericParameter, .function, .optional, .existential, .opaque, .composite: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 84a90275..188f5e8b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -396,9 +396,12 @@ extension FFMSwift2JavaGenerator { // Indirect return receivers. for outParameter in translatedSignature.result.outParameters { - let memoryLayout = renderMemoryLayoutValue(for: outParameter.type) + guard case .concrete(let type) = outParameter.type else { + continue + } + let memoryLayout = renderMemoryLayoutValue(for: type) - let arena = if let className = outParameter.type.className, + let arena = if let className = type.className, analysis.importedTypes[className] != nil { // Use passed-in 'SwiftArena' for 'SwiftValue'. "swiftArena$" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 00fa60e7..438393cb 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -472,6 +472,9 @@ extension FFMSwift2JavaGenerator { genericParameters: genericParameters, genericRequirements: genericRequirements ) + + case .composite: + throw JavaTranslationError.unhandledType(swiftType) } } @@ -691,7 +694,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .genericParameter, .optional, .function, .existential, .opaque: + case .genericParameter, .optional, .function, .existential, .opaque, .composite: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 5655af00..7a071d9a 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -34,9 +34,13 @@ package class ImportedNominalType: ImportedDecl { package var methods: [ImportedFunc] = [] package var variables: [ImportedFunc] = [] package var cases: [ImportedEnumCase] = [] + var inheritedTypes: [SwiftType] - init(swiftNominal: SwiftNominalTypeDeclaration) { + init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { self.swiftNominal = swiftNominal + self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap { + try? SwiftType($0.type, lookupContext: lookupContext) + } ?? [] } var swiftType: SwiftType { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 9351252e..258b537e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -22,6 +22,7 @@ extension JNISwift2JavaGenerator { "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", "java.util.*", + "java.util.concurrent.atomic.AtomicBoolean", // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", @@ -101,6 +102,35 @@ extension JNISwift2JavaGenerator { printPackage(&printer) printImports(&printer) + switch decl.swiftNominal.kind { + case .actor, .class, .enum, .struct: + printConcreteType(&printer, decl) + case .protocol: + printProtocol(&printer, decl) + } + } + + private func printProtocol(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + let extends = ["JNISwiftInstance"] + printer.printBraceBlock("public interface \(decl.swiftNominal.name) extends \(extends.joined(separator: ", "))") { printer in + for initializer in decl.initializers { + printFunctionDowncallMethods(&printer, initializer, signaturesOnly: true) + printer.println() + } + + for method in decl.methods { + printFunctionDowncallMethods(&printer, method, signaturesOnly: true) + printer.println() + } + + for variable in decl.variables { + printFunctionDowncallMethods(&printer, variable, signaturesOnly: true) + printer.println() + } + } + } + + private func printConcreteType(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printNominal(&printer, decl) { printer in printer.print( """ @@ -117,8 +147,18 @@ extension JNISwift2JavaGenerator { printer.print( """ + /** + * The designated constructor of any imported Swift types. + * + * @param selfPointer a pointer to the memory containing the value + * @param swiftArena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ private \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); } /** @@ -136,6 +176,25 @@ extension JNISwift2JavaGenerator { """ ) + printer.print( + """ + /** Pointer to the "self". */ + private final long selfPointer; + + /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ + private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); + + public long $memoryAddress() { + return this.selfPointer; + } + + @Override + public AtomicBoolean $statusDestroyedFlag() { + return $state$destroyed; + } + """ + ) + printer.println() if decl.swiftNominal.kind == .enum { @@ -158,6 +217,8 @@ extension JNISwift2JavaGenerator { printer.println() } + printTypeMetadataAddressFunction(&printer, decl) + printer.println() printDestroyFunction(&printer, decl) } } @@ -194,7 +255,13 @@ extension JNISwift2JavaGenerator { if decl.swiftNominal.isSendable { printer.print("@ThreadSafe // Sendable") } - printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in + var implements = ["JNISwiftInstance"] + implements += decl.inheritedTypes + .compactMap(\.asNominalTypeDeclaration) + .filter { $0.kind == .protocol } + .map(\.name) + let implementsClause = implements.joined(separator: ", ") + printer.printBraceBlock("public final class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in body(&printer) } } @@ -279,14 +346,15 @@ extension JNISwift2JavaGenerator { printer.print("record $NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") } - self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction) + self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, signaturesOnly: false) printer.println() } } private func printFunctionDowncallMethods( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ImportedFunc, + signaturesOnly: Bool = false ) { guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. @@ -297,7 +365,7 @@ extension JNISwift2JavaGenerator { printJavaBindingWrapperHelperClass(&printer, decl) - printJavaBindingWrapperMethod(&printer, decl) + printJavaBindingWrapperMethod(&printer, decl, signaturesOnly: signaturesOnly) } /// Print the helper type container for a user-facing Java API. @@ -340,17 +408,18 @@ extension JNISwift2JavaGenerator { ) } - private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc, signaturesOnly: Bool) { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } - printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl) + printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, signaturesOnly: signaturesOnly) } private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl, - importedFunc: ImportedFunc? = nil + importedFunc: ImportedFunc? = nil, + signaturesOnly: Bool ) { var modifiers = ["public"] if translatedDecl.isStatic { @@ -362,6 +431,19 @@ extension JNISwift2JavaGenerator { var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" + let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in + guard case .generic(let name, let extends) = parameter.parameter.type else { + return + } + generics.append((name, extends)) + } + .map { "\($0) extends \($1.compactMap(\.className).joined(separator: " & "))" } + .joined(separator: ", ") + + if !generics.isEmpty { + modifiers.append("<" + generics + ">") + } + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") if !annotationsStr.isEmpty { annotationsStr += "\n" } @@ -372,9 +454,7 @@ extension JNISwift2JavaGenerator { if let importedFunc { printDeclDocumentation(&printer, importedFunc) } - printer.printBraceBlock( - "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" - ) { printer in + printer.printBraceBlock("\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)") { printer in let globalArenaName = "SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA" let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [globalArenaName] let call = "\(translatedDecl.name)(\(arguments.joined(separator: ", ")))" @@ -393,13 +473,17 @@ extension JNISwift2JavaGenerator { if let importedFunc { printDeclDocumentation(&printer, importedFunc) } - printer.printBraceBlock( - "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" - ) { printer in - printDowncall(&printer, translatedDecl) + let signature = "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" + if signaturesOnly { + printer.print("\(signature);") + } else { + printer.printBraceBlock(signature) { printer in + printDowncall(&printer, translatedDecl) + } + + printNativeFunction(&printer, translatedDecl) } - printNativeFunction(&printer, translatedDecl) } private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { @@ -470,13 +554,33 @@ extension JNISwift2JavaGenerator { ) } + private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printer.print("private static native long $typeMetadataAddressDowncall();") + + let funcName = "$typeMetadataAddress" + printer.print("@Override") + printer.printBraceBlock("public long $typeMetadataAddress()") { printer in + printer.print( + """ + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", + "this", this, + "self", self$); + } + return \(type.swiftNominal.name).$typeMetadataAddressDowncall(); + """ + ) + } + } + /// Prints the destroy function for a `JNISwiftInstance` private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { printer.print("private static native void $destroy(long selfPointer);") let funcName = "$createDestroyFunction" printer.print("@Override") - printer.printBraceBlock("protected Runnable \(funcName)()") { printer in + printer.printBraceBlock("public Runnable \(funcName)()") { printer in printer.print( """ long self$ = this.$memoryAddress(); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 073883a2..a0178d3c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -29,7 +29,8 @@ extension JNISwift2JavaGenerator { config: config, swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable) ) translated = try translation.translate(decl) } catch { @@ -54,7 +55,8 @@ extension JNISwift2JavaGenerator { config: config, swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable) ) translated = try translation.translate(enumCase: decl) } catch { @@ -71,12 +73,14 @@ extension JNISwift2JavaGenerator { let swiftModuleName: String let javaPackage: String let javaClassLookupTable: JavaClassLookupTable + var knownTypes: SwiftKnownTypes func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase { let nativeTranslation = NativeJavaTranslation( config: self.config, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: self.knownTypes ) let methodName = "" // TODO: Used for closures, replace with better name? @@ -85,7 +89,9 @@ extension JNISwift2JavaGenerator { let translatedValues = try self.translateParameters( enumCase.parameters.map { ($0.name, $0.type) }, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: [], + genericRequirements: [] ) let conversions = try enumCase.parameters.enumerated().map { idx, parameter in @@ -160,7 +166,8 @@ extension JNISwift2JavaGenerator { let nativeTranslation = NativeJavaTranslation( config: self.config, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: self.knownTypes ) // Types with no parent will be outputted inside a "module" class. @@ -228,7 +235,15 @@ extension JNISwift2JavaGenerator { for (i, param) in swiftType.parameters.enumerated() { let paramName = param.parameterName ?? "_\(i)" translatedParams.append( - try translateParameter(swiftType: param.type, parameterName: paramName, methodName: name, parentName: parentName) + try translateParameter( + swiftType: param.type, + parameterName: paramName, + methodName: name, + parentName: parentName, + genericParameters: [], + genericRequirements: [], + parameterPosition: nil + ) ) } @@ -250,11 +265,19 @@ extension JNISwift2JavaGenerator { let parameters = try translateParameters( functionSignature.parameters.map { ($0.parameterName, $0.type )}, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements ) // 'self' - let selfParameter = try self.translateSelfParameter(functionSignature.selfParameter, methodName: methodName, parentName: parentName) + let selfParameter = try self.translateSelfParameter( + functionSignature.selfParameter, + methodName: methodName, + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements + ) let resultType = try translate(swiftResult: functionSignature.result) @@ -268,22 +291,43 @@ extension JNISwift2JavaGenerator { func translateParameters( _ parameters: [(name: String?, type: SwiftType)], methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> [TranslatedParameter] { - try parameters.enumerated().map { idx, param in + try parameters.enumerated().map { + idx, + param in let parameterName = param.name ?? "arg\(idx)" - return try translateParameter(swiftType: param.type, parameterName: parameterName, methodName: methodName, parentName: parentName) + return try translateParameter( + swiftType: param.type, + parameterName: parameterName, + methodName: methodName, + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + parameterPosition: idx + ) } } - func translateSelfParameter(_ selfParameter: SwiftSelfParameter?, methodName: String, parentName: String) throws -> TranslatedParameter? { + func translateSelfParameter( + _ selfParameter: SwiftSelfParameter?, + methodName: String, + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) throws -> TranslatedParameter? { // 'self' if case .instance(let swiftSelf) = selfParameter { return try self.translateParameter( swiftType: swiftSelf.type, parameterName: swiftSelf.parameterName ?? "self", methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + parameterPosition: nil ) } else { return nil @@ -294,7 +338,10 @@ extension JNISwift2JavaGenerator { swiftType: SwiftType, parameterName: String, methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement], + parameterPosition: Int? ) throws -> TranslatedParameter { // If the result type should cause any annotations on the method, include them here. @@ -354,7 +401,7 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: nominalTypeName), + type: .concrete(.class(package: nil, name: nominalTypeName)), annotations: parameterAnnotations ), conversion: .valueMemoryAddress(.placeholder) @@ -382,7 +429,29 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) - case .metatype, .tuple, .existential, .opaque, .genericParameter: + case .opaque(let proto), .existential(let proto): + guard let parameterPosition else { + fatalError("Cannot extract opaque or existential type that is not a parameter: \(proto)") + } + + return try translateProtocolParameter( + protocolType: proto, + parameterName: parameterName, + javaGenericName: "$T\(parameterPosition)" + ) + + case .genericParameter(let generic): + if let concreteTy = swiftType.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try translateProtocolParameter( + protocolType: concreteTy, + parameterName: parameterName, + javaGenericName: generic.name + ) + } + + throw JavaTranslationError.unsupportedSwiftType(swiftType) + + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -401,6 +470,50 @@ extension JNISwift2JavaGenerator { } } + func translateProtocolParameter( + protocolType: SwiftType, + parameterName: String, + javaGenericName: String + ) throws -> TranslatedParameter { + switch protocolType { + case .nominal: + return try translateProtocolParameter(protocolTypes: [protocolType], parameterName: parameterName, javaGenericName: javaGenericName) + + case .composite(let types): + return try translateProtocolParameter(protocolTypes: types, parameterName: parameterName, javaGenericName: javaGenericName) + + default: + throw JavaTranslationError.unsupportedSwiftType(protocolType) + } + } + + private func translateProtocolParameter( + protocolTypes: [SwiftType], + parameterName: String, + javaGenericName: String + ) throws -> TranslatedParameter { + let javaProtocolTypes = try protocolTypes.map { + switch $0 { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + return JavaType.class(package: nil, name: nominalTypeName) + + default: + throw JavaTranslationError.unsupportedSwiftType($0) + } + } + + // We assume this is a JExtract class. + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .generic(name: javaGenericName, extends: javaProtocolTypes), + annotations: [] + ), + conversion: .commaSeparated([.valueMemoryAddress(.placeholder), .typeMetadataAddress(.placeholder)]) + ) + } + func translateOptionalParameter( wrappedType swiftType: SwiftType, parameterName: String @@ -519,7 +632,7 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) - case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -743,6 +856,9 @@ extension JNISwift2JavaGenerator { /// `value.$memoryAddress()` indirect case valueMemoryAddress(JavaNativeConversionStep) + /// `value.$typeMetadataAddress()` + indirect case typeMetadataAddress(JavaNativeConversionStep) + /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) @@ -816,6 +932,10 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" + case .typeMetadataAddress(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).$typeMetadataAddress()" + case .constructSwiftValue(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" @@ -922,6 +1042,9 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + case .typeMetadataAddress(let inner): + return inner.requiresSwiftArena + case .commaSeparated(let list): return list.contains(where: { $0.requiresSwiftArena }) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 03273e68..bdbfe2f1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -21,6 +21,7 @@ extension JNISwift2JavaGenerator { let config: Configuration let javaPackage: String let javaClassLookupTable: JavaClassLookupTable + var knownTypes: SwiftKnownTypes /// Translates a Swift function into the native JNI method signature. func translate( @@ -33,22 +34,26 @@ extension JNISwift2JavaGenerator { translatedParameter, swiftParameter in let parameterName = translatedParameter.parameter.name - return try translate( - swiftParameter: swiftParameter, + return try translateParameter( + type: swiftParameter.type, parameterName: parameterName, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements ) } // Lower the self parameter. let nativeSelf: NativeParameter? = switch functionSignature.selfParameter { case .instance(let selfParameter): - try translate( - swiftParameter: selfParameter, + try translateParameter( + type: selfParameter.type, parameterName: selfParameter.parameterName ?? "self", methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements ) case nil, .initializer(_), .staticMethod(_): nil @@ -65,26 +70,32 @@ extension JNISwift2JavaGenerator { _ parameters: [SwiftParameter], translatedParameters: [TranslatedParameter], methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> [NativeParameter] { try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in let parameterName = translatedParameter.parameter.name - return try translate( - swiftParameter: swiftParameter, + return try translateParameter( + type: swiftParameter.type, parameterName: parameterName, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } } - func translate( - swiftParameter: SwiftParameter, + func translateParameter( + type: SwiftType, parameterName: String, methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> NativeParameter { - switch swiftParameter.type { + switch type { case .nominal(let nominalType): let nominalTypeName = nominalType.nominalTypeDecl.name @@ -92,7 +103,7 @@ extension JNISwift2JavaGenerator { switch knownType { case .optional: guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + throw JavaTranslationError.unsupportedSwiftType(type) } return try translateOptionalParameter( wrappedType: genericArgs[0], @@ -102,14 +113,14 @@ extension JNISwift2JavaGenerator { default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + throw JavaTranslationError.unsupportedSwiftType(type) } return NativeParameter( parameters: [ JavaParameter(name: parameterName, type: javaType) ], - conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) + conversion: .initFromJNI(.placeholder, swiftType: type) ) } @@ -117,7 +128,7 @@ extension JNISwift2JavaGenerator { if nominalType.isJavaKitWrapper { guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { - throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftParameter.type) + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(type) } return NativeParameter( @@ -140,7 +151,7 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: parameterName, type: .long) ], - conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) + conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: type)) ) case .tuple([]): @@ -180,11 +191,68 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) - case .metatype, .tuple, .existential, .opaque, .genericParameter: - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + case .opaque(let proto), .existential(let proto): + return try translateProtocolParameter( + protocolType: proto, + parameterName: parameterName + ) + + case .genericParameter: + if let concreteTy = type.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try translateProtocolParameter( + protocolType: concreteTy, + parameterName: parameterName + ) + } + + throw JavaTranslationError.unsupportedSwiftType(type) + + case .metatype, .tuple, .composite: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + + func translateProtocolParameter( + protocolType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + switch protocolType { + case .nominal(let nominalType): + let protocolName = nominalType.nominalTypeDecl.qualifiedName + return try translateProtocolParameter(protocolNames: [protocolName], parameterName: parameterName) + + case .composite(let types): + let protocolNames = try types.map { + guard let nominalTypeName = $0.asNominalType?.nominalTypeDecl.qualifiedName else { + throw JavaTranslationError.unsupportedSwiftType($0) + } + return nominalTypeName + } + + return try translateProtocolParameter(protocolNames: protocolNames, parameterName: parameterName) + + default: + throw JavaTranslationError.unsupportedSwiftType(protocolType) } } + private func translateProtocolParameter( + protocolNames: [String], + parameterName: String + ) throws -> NativeParameter { + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .long), + JavaParameter(name: "\(parameterName)_typeMetadataAddress", type: .long) + ], + conversion: .extractSwiftProtocolValue( + .placeholder, + typeMetadataVariableName: .combinedName(component: "typeMetadataAddress"), + protocolNames: protocolNames + ) + ) + } + func translateOptionalParameter( wrappedType swiftType: SwiftType, parameterName: String @@ -351,7 +419,7 @@ extension JNISwift2JavaGenerator { outParameters: [] ) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -380,7 +448,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -432,7 +500,7 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) - case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } @@ -480,6 +548,12 @@ extension JNISwift2JavaGenerator { /// `SwiftType(from: value, in: environment!)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) + indirect case extractSwiftProtocolValue( + NativeSwiftConversionStep, + typeMetadataVariableName: NativeSwiftConversionStep, + protocolNames: [String] + ) + /// Extracts a swift type at a pointer given by a long. indirect case extractSwiftValue( NativeSwiftConversionStep, @@ -540,6 +614,35 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment!)" + case .extractSwiftProtocolValue(let inner, let typeMetadataVariableName, let protocolNames): + let inner = inner.render(&printer, placeholder) + let typeMetadataVariableName = typeMetadataVariableName.render(&printer, placeholder) + let existentialName = "\(inner)Existential$" + + let compositeProtocolName = "(\(protocolNames.joined(separator: " & ")))" + + // TODO: Remove the _openExistential when we decide to only support language mode v6+ + printer.print( + """ + guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment!))) else { + fatalError("\(typeMetadataVariableName) memory address was null") + } + let \(inner)DynamicType$: Any.Type = unsafeBitCast(\(inner)TypeMetadataPointer$, to: Any.Type.self) + guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment!))) else { + fatalError("\(inner) memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let \(existentialName) = \(inner)RawPointer$.load(as: \(inner)DynamicType$) as! any \(compositeProtocolName) + #else + func \(inner)DoLoad(_ ty: Ty.Type) -> any \(compositeProtocolName) { + \(inner)RawPointer$.load(as: ty) as! any \(compositeProtocolName) + } + let \(existentialName) = _openExistential(\(inner)DynamicType$, do: \(inner)DoLoad) + #endif + """ + ) + return existentialName + case .extractSwiftValue(let inner, let swiftType, let allowNil): let inner = inner.render(&printer, placeholder) let pointerName = "\(inner)$" @@ -584,7 +687,14 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature( resultType: nativeResult.javaType, - parameterTypes: parameters.flatMap { $0.parameters.map(\.type) } + parameterTypes: parameters.flatMap { + $0.parameters.map { parameter in + guard case .concrete(let type) = parameter.type else { + fatalError("Closures do not support Java generics") + } + return type + } + } ) let names = parameters.flatMap { $0.parameters.map(\.name) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 329b7dfd..3848ceac 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -109,6 +109,15 @@ extension JNISwift2JavaGenerator { printJNICache(&printer, type) printer.println() + switch type.swiftNominal.kind { + case .actor, .class, .enum, .struct: + printConcreteTypeThunks(&printer, type) + case .protocol: + printProtocolThunks(&printer, type) + } + } + + private func printConcreteTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { for initializer in type.initializers { printSwiftFunctionThunk(&printer, initializer) printer.println() @@ -134,9 +143,16 @@ extension JNISwift2JavaGenerator { printer.println() } + printTypeMetadataAddressThunk(&printer, type) + printer.println() printDestroyFunctionThunk(&printer, type) } + private func printProtocolThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let protocolName = type.swiftNominal.name + } + + private func printEnumDiscriminator(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( @@ -415,6 +431,23 @@ extension JNISwift2JavaGenerator { ) } + private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printCDecl( + &printer, + javaMethodName: "$typeMetadataAddressDowncall", + parentName: type.swiftNominal.name, + parameters: [], + resultType: .long + ) { printer in + printer.print( + """ + let metadataPointer = unsafeBitCast(\(type.swiftNominal.qualifiedName).self, to: UnsafeRawPointer.self) + return Int64(Int(bitPattern: metadataPointer)).getJNIValue(in: environment) + """ + ) + } + } + /// Prints the implementation of the destroy function. private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index f9b99f6b..a677bcde 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -28,6 +28,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String + let lookupContext: SwiftTypeLookupContext let javaClassLookupTable: JavaClassLookupTable @@ -61,6 +62,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory self.javaClassLookupTable = javaClassLookupTable + self.lookupContext = translator.lookupContext // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index a12b13b2..43f5a2b4 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -16,17 +16,56 @@ import JavaTypes /// Represent a parameter in Java code. struct JavaParameter { - let name: String - let type: JavaType + enum ParameterType: CustomStringConvertible { + case concrete(JavaType) + case generic(name: String, extends: [JavaType]) + + var jniTypeSignature: String { + switch self { + case .concrete(let type): + return type.jniTypeSignature + case .generic(_, let extends): + guard !extends.isEmpty else { + return "Ljava/lang/Object;" + } + + // Generics only use the first type for JNI + return extends.first!.jniTypeSignature + } + } + + var jniTypeName: String { + switch self { + case .concrete(let type): type.jniTypeName + case .generic: "jobject?" + } + } + + var description: String { + switch self { + case .concrete(let type): type.description + case .generic(let name, _): name + } + } + } + var name: String + var type: ParameterType + /// Parameter annotations are used in parameter declarations like this: `@Annotation int example` let annotations: [JavaAnnotation] - init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { + init(name: String, type: ParameterType, annotations: [JavaAnnotation] = []) { self.name = name self.type = type self.annotations = annotations } + init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { + self.name = name + self.type = .concrete(type) + self.annotations = annotations + } + func renderParameter() -> String { if annotations.isEmpty { return "\(type) \(name)" diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 02049ed4..ccbbd274 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -146,6 +146,8 @@ extension Swift2JavaTranslator { return check(ty) case .existential(let ty), .opaque(let ty): return check(ty) + case .composite(let types): + return types.contains(where: check) case .genericParameter: return false } @@ -200,7 +202,7 @@ extension Swift2JavaTranslator { _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax, parent: ImportedNominalType? ) -> ImportedNominalType? { - if !nominalNode.shouldExtract(config: config, log: log) { + if !nominalNode.shouldExtract(config: config, log: log, in: parent) { return nil } @@ -225,7 +227,7 @@ extension Swift2JavaTranslator { guard swiftNominalDecl.moduleName == self.swiftModuleName else { return nil } - guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log) else { + guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log, in: nil) else { return nil } @@ -239,7 +241,7 @@ extension Swift2JavaTranslator { return alreadyImported } - let importedNominal = ImportedNominalType(swiftNominal: nominal) + let importedNominal = try? ImportedNominalType(swiftNominal: nominal, lookupContext: lookupContext) importedTypes[fullName] = importedNominal return importedNominal diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 9b183cb6..13185a5c 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -105,7 +105,7 @@ final class Swift2JavaVisitor { } func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldExtract(config: config, log: log) else { + guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -183,7 +183,7 @@ final class Swift2JavaVisitor { } func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldExtract(config: config, log: log) else { + guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -237,7 +237,7 @@ final class Swift2JavaVisitor { self.log.info("Initializer must be within a current type; \(node)") return } - guard node.shouldExtract(config: config, log: log) else { + guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -293,10 +293,10 @@ final class Swift2JavaVisitor { } extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { - func shouldExtract(config: Configuration, log: Logger) -> Bool { + func shouldExtract(config: Configuration, log: Logger, in parent: ImportedNominalType?) -> Bool { let meetsRequiredAccessLevel: Bool = switch config.effectiveMinimumInputAccessLevelMode { - case .public: self.isPublic + case .public: self.isPublic(in: parent?.swiftNominal.syntax) case .package: self.isAtLeastPackage case .internal: self.isAtLeastInternal } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 335979a4..763a5da2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -93,6 +93,10 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { return firstInheritanceType.type }() + var inheritanceTypes: InheritedTypeListSyntax? { + self.syntax?.inheritanceClause?.inheritedTypes + } + /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". lazy var isSendable: Bool = { // Check if Sendable is in the inheritance list diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift similarity index 72% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift index ce668485..1a658513 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift @@ -27,6 +27,38 @@ extension SwiftType { genericRequirements: genericRequirements ) } + + /// Returns the protocol type if this is a generic parameter in the list + func typeIn( + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) -> SwiftType? { + switch self { + case .genericParameter(let genericParam): + if genericParameters.contains(genericParam) { + let types: [SwiftType] = genericRequirements.compactMap { + guard case .inherits(let left, let right) = $0, left == self else { + return nil + } + return right + } + + if types.isEmpty { + // TODO: Any?? + return nil + } else if types.count == 1 { + return types.first! + } else { + return .composite(types) + } + } + + return nil + + default: + return nil + } + } } private func representativeConcreteType( diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 58bb65c3..81afe637 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -37,6 +37,9 @@ enum SwiftType: Equatable { /// `some ` indirect case opaque(SwiftType) + /// `type1` & `type2` + indirect case composite([SwiftType]) + static var void: Self { return .tuple([]) } @@ -45,7 +48,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .genericParameter, .function, .metatype, .optional, .existential, .opaque: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite: nil } } @@ -88,7 +91,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .genericParameter, .optional, .tuple, .existential, .opaque: + case .genericParameter, .optional, .tuple, .existential, .opaque, .composite: return false } } @@ -123,7 +126,7 @@ extension SwiftType: CustomStringConvertible { /// requires parentheses. private var postfixRequiresParentheses: Bool { switch self { - case .function, .existential, .opaque: true + case .function, .existential, .opaque, .composite: true case .genericParameter, .metatype, .nominal, .optional, .tuple: false } } @@ -147,6 +150,8 @@ extension SwiftType: CustomStringConvertible { return "any \(constraintType)" case .opaque(let constraintType): return "some \(constraintType)" + case .composite(let types): + return types.map(\.description).joined(separator: " & ") } } } @@ -208,7 +213,7 @@ extension SwiftNominalType { extension SwiftType { init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { - case .arrayType, .classRestrictionType, .compositionType, + case .arrayType, .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, .packElementType, .packExpansionType, .suppressedType: throw TypeTranslationError.unimplementedType(type) @@ -311,6 +316,13 @@ extension SwiftType { } else { self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } + + case .compositionType(let compositeType): + let types = try compositeType.elements.map { + try SwiftType($0.type, lookupContext: lookupContext) + } + + self = .composite(types) } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 8d193149..5884270a 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -60,10 +60,17 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | | Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | -| Generic functions | ❌ | ❌ | -| `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | +| Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | +| Generic return values in functions: `func f() -> T` | ❌ | ❌ | | Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | -| Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | +| Protocols: `protocol` | ❌ | ✅ | +| Protocols: `protocol` with associated types | ❌ | ❌ | +| Existential parameters `f(x: any SomeProtocol) ` | ❌ | ✅ | +| Existential parameters `f(x: any (A & B)) ` | ❌ | ✅ | +| Existential return types `f() -> any Collection ` | ❌ | ❌ | +| Foundation Data and DataProtocol: `f(x: any DataProtocol) -> Data` | ✅ | ❌ | +| Opaque parameters: `func take(worker: some Builder) -> some Builder` | ❌ | ✅ | +| Opaque return types: `func get() -> some Builder` | ❌ | ❌ | | Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | | Optional return types: `func f() -> Int?`, `func g() -> MyClass?` | ❌ | ✅ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | @@ -90,7 +97,6 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | | Value semantic types (e.g. struct copying) | ❌ | ❌ | -| Opaque types: `func get() -> some Builder`, func take(worker: some Worker) | ❌ | ❌ | | Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | | | | | | | | | @@ -266,5 +272,47 @@ try (var arena = SwiftArena.ofConfined()) { } ``` +### Protocols +> Note: Protocols are currently only supported in JNI mode. +> +> With the exception of `any DataProtocol` which is handled as `Foundation.Data` in the FFM mode. +Swift `protocol` types are imported as Java `interface`s. For now, we require that all +concrete types of an interface wrap a Swift instance. In the future, we will add support +for providing Java-based implementations of interfaces, that you can pass to Java functions. + +Consider the following Swift protocol: +```swift +protocol Named { + var name: String { get } + + func describe() -> String +} +``` +will be exported as +```java +interface Named extends JNISwiftInstance { + public String getName(); + + public String describe(); +} +``` + +#### Parameters +Any opaque, existential or generic parameters are imported as Java generics. +This means that the following function: +```swift +func f(x: S, y: any C, z: some D) +``` +will be exported as +```java + void f(S x, T1 y, T2 z) +``` +On the Java side, only SwiftInstance implementing types may be passed; +so this isn't a way for compatibility with just any arbitrary Java interfaces, +but specifically, for allowing passing concrete binding types generated by jextract from Swift types +which conform a to a given Swift protocol. + +#### Returning protocol types +Protocols are not yet supported as return types. diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index 95f1e5a0..4e3b8378 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -14,32 +14,7 @@ package org.swift.swiftkit.core; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class JNISwiftInstance extends SwiftInstance { - // Pointer to the "self". - protected final long selfPointer; - - /** - * The designated constructor of any imported Swift types. - * - * @param selfPointer a pointer to the memory containing the value - * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. - */ - protected JNISwiftInstance(long selfPointer, SwiftArena arena) { - SwiftObjects.requireNonZero(selfPointer, "selfPointer"); - this.selfPointer = selfPointer; - - // Only register once we have fully initialized the object since this will need the object pointer. - arena.register(this); - } - - @Override - public long $memoryAddress() { - return this.selfPointer; - } - +public interface JNISwiftInstance extends SwiftInstance { /** * Creates a function that will be called when the value should be destroyed. * This will be code-generated to call a native method to do deinitialization and deallocation. @@ -52,10 +27,12 @@ protected JNISwiftInstance(long selfPointer, SwiftArena arena) { * * @return a function that is called when the value should be destroyed. */ - protected abstract Runnable $createDestroyFunction(); + Runnable $createDestroyFunction(); + + long $typeMetadataAddress(); @Override - public SwiftInstanceCleanup $createCleanup() { + default SwiftInstanceCleanup $createCleanup() { var statusDestroyedFlag = $statusDestroyedFlag(); Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java index 44955cc6..3ab07ffb 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java @@ -16,12 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean; -public abstract class SwiftInstance { - - // 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); - +public interface SwiftInstance { /** * Pointer to the {@code self} of the underlying Swift object or value. * @@ -29,14 +24,14 @@ public abstract class SwiftInstance { * is kept alive using some means (e.g. a class remains retained), as * this function does not ensure safety of the address in any way. */ - public abstract long $memoryAddress(); + long $memoryAddress(); /** * Called when the arena has decided the value should be destroyed. *

* Warning: The cleanup action must not capture {@code this}. */ - public abstract SwiftInstanceCleanup $createCleanup(); + SwiftInstanceCleanup $createCleanup(); /** * Exposes a boolean value which can be used to indicate if the object was destroyed. @@ -45,10 +40,7 @@ public abstract class SwiftInstance { * 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 {@code AutoSwiftMemorySession}. */ - public final AtomicBoolean $statusDestroyedFlag() { - return this.$state$destroyed; - } - + AtomicBoolean $statusDestroyedFlag(); /** * Ensures that this instance has not been destroyed. *

@@ -56,8 +48,8 @@ public abstract class SwiftInstance { * to be thrown. This check should be performed before accessing {@code $memorySegment} to prevent * use-after-free errors. */ - protected final void $ensureAlive() { - if (this.$state$destroyed.get()) { + default void $ensureAlive() { + if (this.$statusDestroyedFlag().get()) { throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!"); } } diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java index b414962f..12093421 100644 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java @@ -18,6 +18,8 @@ import org.swift.swiftkit.core.JNISwiftInstance; import org.swift.swiftkit.core.SwiftArena; +import java.util.concurrent.atomic.AtomicBoolean; + public class AutoArenaTest { @Test @@ -45,13 +47,30 @@ public void cleaner_releases_native_resource() { } } - private static class FakeSwiftInstance extends JNISwiftInstance { + private static class FakeSwiftInstance implements JNISwiftInstance { + AtomicBoolean $state$destroyed = new AtomicBoolean(false); + public FakeSwiftInstance(SwiftArena arena) { - super(1, arena); + arena.register(this); } - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { return () -> {}; } + + @Override + public long $typeMetadataAddress() { + return 0; + } + + @Override + public long $memoryAddress() { + return 0; + } + + @Override + public AtomicBoolean $statusDestroyedFlag() { + return $state$destroyed; + } } } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java index 1236bad2..70324c94 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java @@ -18,10 +18,15 @@ import org.swift.swiftkit.core.SwiftInstanceCleanup; import java.lang.foreign.MemorySegment; +import java.util.concurrent.atomic.AtomicBoolean; -public abstract class FFMSwiftInstance extends SwiftInstance { +public abstract class FFMSwiftInstance implements SwiftInstance { private final MemorySegment memorySegment; + // 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); + /** * The designated constructor of any imported Swift types. * @@ -52,6 +57,16 @@ protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { */ public abstract SwiftAnyType $swiftType(); + /** + * 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 {@code AutoSwiftMemorySession}. + */ + public final AtomicBoolean $statusDestroyedFlag() { + return this.$state$destroyed; + } @Override public SwiftInstanceCleanup $createCleanup() { @@ -65,7 +80,6 @@ protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { ); } - /** * Returns `true` if this swift instance is a reference type, i.e. a `class` or (`distributed`) `actor`. * diff --git a/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java index b95f6c45..18c0a5af 100644 --- a/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java +++ b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java @@ -18,6 +18,7 @@ import java.lang.foreign.GroupLayout; import java.lang.foreign.MemorySegment; +import java.util.concurrent.atomic.AtomicBoolean; public class AutoArenaTest { diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 30bb7433..a7527aa8 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -55,9 +55,12 @@ struct JNIClassTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; + import java.util.*; + import java.util.concurrent.atomic.AtomicBoolean; + import org.swift.swiftkit.core.annotations.*; """, """ - public final class MyClass extends JNISwiftInstance { + public final class MyClass implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -68,9 +71,21 @@ struct JNIClassTests { } """, """ - private MyClass(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } + private final long selfPointer; + """, + """ + public long $memoryAddress() { + return this.selfPointer; + } + """, + """ + private MyClass(long selfPointer, SwiftArena swiftArena) { + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); + } """, """ public static MyClass wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { @@ -92,7 +107,7 @@ struct JNIClassTests { expectedChunks: [ """ @Override - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { long self$ = this.$memoryAddress(); if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("MyClass.$createDestroyFunction", diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 47765043..c6aaf923 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -39,9 +39,12 @@ struct JNIEnumTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; + import java.util.*; + import java.util.concurrent.atomic.AtomicBoolean; + import org.swift.swiftkit.core.annotations.*; """, """ - public final class MyEnum extends JNISwiftInstance { + public final class MyEnum implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -53,7 +56,19 @@ struct JNIEnumTests { """, """ private MyEnum(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); + } + """, + """ + private final long selfPointer; + """, + """ + public long $memoryAddress() { + return this.selfPointer; } """, """ @@ -66,7 +81,7 @@ struct JNIEnumTests { """, """ @Override - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { long self$ = this.$memoryAddress(); if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("MyEnum.$createDestroyFunction", diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index ad55f491..27b0cdea 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -46,6 +46,7 @@ struct JNIModuleTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; import java.util.*; + import java.util.concurrent.atomic.AtomicBoolean; import org.swift.swiftkit.core.annotations.*; public final class SwiftModule { diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index 548a2eac..be2e0f6a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -127,12 +127,12 @@ struct JNIOptionalTests { result$ = innerResult$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 + } else { result$ = String.jniPlaceholderValue var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 + } return result$ } """ @@ -190,12 +190,12 @@ struct JNIOptionalTests { result$ = _resultBits$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 + } else { result$ = 0 var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 + } return result$ } """ @@ -243,7 +243,7 @@ struct JNIOptionalTests { func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { return JavaLong(javaThis: $0, environment: environment!) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:691 + } ) } """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift new file mode 100644 index 00000000..b5a0fcdb --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -0,0 +1,246 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIProtocolTests { + let source = """ + public protocol SomeProtocol { + var x: Int64 { get set } + + public func method() {} + } + + public protocol B {} + + public class SomeClass: SomeProtocol {} + + public func takeProtocol(x: some SomeProtocol, y: any SomeProtocol) + public func takeGeneric(s: S) + public func takeComposite(x: any SomeProtocol & B) + """ + + @Test + func generatesJavaInterface() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """, + """ + public interface SomeProtocol extends JNISwiftInstance { + ... + } + """, + """ + public long getX(); + """, + """ + public void setX(long newValue); + """, + """ + public void method(); + """ + ]) + } + + @Test + func generatesJavaClassWithExtends() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class SomeClass implements JNISwiftInstance, SomeProtocol { + """ + ]) + } + + @Test + func takeProtocol_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static <$T0 extends SomeProtocol, $T1 extends SomeProtocol> void takeProtocol($T0 x, $T1 y) { + SwiftModule.$takeProtocol(x.$memoryAddress(), x.$typeMetadataAddress(), y.$memoryAddress(), y.$typeMetadataAddress()); + } + """, + """ + private static native void $takeProtocol(long x, long x_typeMetadataAddress, long y, long y_typeMetadataAddress); + """ + ]) + } + + @Test + func takeProtocol_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ") + func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + fatalError("x_typeMetadataAddress memory address was null") + } + let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + fatalError("x memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol) + #else + func xDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { + xRawPointer$.load(as: ty) as! any (SomeProtocol) + } + let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) + #endif + guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment!))) else { + fatalError("y_typeMetadataAddress memory address was null") + } + let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self) + guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment!))) else { + fatalError("y memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let yExistential$ = yRawPointer$.load(as: yDynamicType$) as! any (SomeProtocol) + #else + func yDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { + yRawPointer$.load(as: ty) as! any (SomeProtocol) + } + let yExistential$ = _openExistential(yDynamicType$, do: yDoLoad) + #endif + SwiftModule.takeProtocol(x: xExistential$, y: yExistential$) + } + """ + ] + ) + } + + @Test + func takeGeneric_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static void takeGeneric(S s) { + SwiftModule.$takeGeneric(s.$memoryAddress(), s.$typeMetadataAddress()); + } + """, + """ + private static native void $takeGeneric(long s, long s_typeMetadataAddress); + """ + ]) + } + + @Test + func takeGeneric_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ") + func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) { + guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment!))) else { + fatalError("s_typeMetadataAddress memory address was null") + } + let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self) + guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment!))) else { + fatalError("s memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let sExistential$ = sRawPointer$.load(as: sDynamicType$) as! any (SomeProtocol) + #else + func sDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { + sRawPointer$.load(as: ty) as! any (SomeProtocol) + } + let sExistential$ = _openExistential(sDynamicType$, do: sDoLoad) + #endif + SwiftModule.takeGeneric(s: sExistential$) + } + """ + ] + ) + } + + @Test + func takeComposite_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static <$T0 extends SomeProtocol & B> void takeComposite($T0 x) { + SwiftModule.$takeComposite(x.$memoryAddress(), x.$typeMetadataAddress()); + } + """, + """ + private static native void $takeComposite(long x, long x_typeMetadataAddress); + """ + ]) + } + + @Test + func takeComposite_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ") + func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + fatalError("x_typeMetadataAddress memory address was null") + } + let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + fatalError("x memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol & B) + #else + func xDoLoad(_ ty: Ty.Type) -> any (SomeProtocol & B) { + xRawPointer$.load(as: ty) as! any (SomeProtocol & B) + } + let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) + #endif + SwiftModule.takeComposite(x: xExistential$) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 7f54fa7e..a7c689aa 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -47,7 +47,7 @@ struct JNIStructTests { """,]) try assertOutput(input: source, .jni, .java, expectedChunks: [ """ - public final class MyStruct extends JNISwiftInstance { + public final class MyStruct implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -58,9 +58,13 @@ struct JNIStructTests { } """, """ - private MyStruct(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } + private MyStruct(long selfPointer, SwiftArena swiftArena) { + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); + } """, """ public static MyStruct wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { @@ -80,7 +84,7 @@ struct JNIStructTests { expectedChunks: [ """ @Override - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { long self$ = this.$memoryAddress(); if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("MyStruct.$createDestroyFunction", diff --git a/Tests/JExtractSwiftTests/SendableTests.swift b/Tests/JExtractSwiftTests/SendableTests.swift index d6d0d2d6..5116fc03 100644 --- a/Tests/JExtractSwiftTests/SendableTests.swift +++ b/Tests/JExtractSwiftTests/SendableTests.swift @@ -46,11 +46,11 @@ final class SendableTests { expectedChunks: [ """ @ThreadSafe // Sendable - public final class SendableStruct extends JNISwiftInstance { + public final class SendableStruct implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; """, ] ) } -} \ No newline at end of file +} From 67c8153984abbcdbfa258006f9e109b10c500264 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 29 Aug 2025 16:55:17 +0900 Subject: [PATCH 141/178] Add destructuring pattern match example to enum docs (#381) --- .../Documentation.docc/SupportedFeatures.md | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 5884270a..d4c12083 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -209,10 +209,10 @@ Vehicle vehicle = ...; switch (vehicle.getDiscriminator()) { case BICYCLE: System.out.println("I am a bicycle!"); - break + break; case CAR: System.out.println("I am a car!"); - break + break; } ``` If you also want access to the associated values, you have various options @@ -223,12 +223,24 @@ Vehicle vehicle = ...; switch (vehicle.getCase()) { case Vehicle.Bicycle b: System.out.println("Bicycle maker: " + b.maker()); - break + break; case Vehicle.Car c: System.out.println("Car: " + c.arg0()); - break + break; } ``` +or even, destructuring the records in the switch statement's pattern match directly: +```java +Vehicle vehicle = ...; +switch (vehicle.getCase()) { + case Vehicle.Car(var name, var unused): + System.out.println("Car: " + name); + break; + default: + break; +} +``` + For Java 16+ you can use [pattern matching for instanceof](https://openjdk.org/jeps/394) ```java Vehicle vehicle = ...; From 899dc23a992eb0265215c8b4bc3fb50b8d012983 Mon Sep 17 00:00:00 2001 From: David Ko Date: Fri, 29 Aug 2025 21:51:27 -0400 Subject: [PATCH 142/178] Update README and SupportedFeatures (#383) * update gitgnore * align with README JExtract from Java to Swift * add watch to 'you may want to this' * add intro to SwiftJavaDocumentation index.md * add intro to SwiftJavaDocumentation index.md * add intro to SwiftJavaDocumentation index.md * align documentation with module rename * SwiftJavaDocumentation correction * Revert "SwiftJavaDocumentation correction" This reverts commit f65061cf242697f10cbd738fbe76a21875c57a39. * revert ConfigureCommand formatting * revert SupportFeatures.md * update benchmark sample project name * renaming JavaKit to SwiftJava * renaming JavaKit to SwiftJava in SupportedFeatures --- README.md | 12 ++++++------ .../Documentation.docc/SupportedFeatures.md | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c15db541..52c61bb4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains two approaches to Swift/Java interoperability. -- Swift library (`JavaKit`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. +- Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. ## :construction: :construction: :construction: Early Development :construction: :construction: :construction: @@ -19,11 +19,11 @@ The primary purpose of this repository is to create an environment for collabora This project consists of different modules which have different Swift and Java runtime requirements. -## JavaKit macros +## SwiftJava macros -JavaKit is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. +SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. -It is possible to generate Swift bindings to Java libraries using JavaKit by using the `swift-java wrap-java` command. +It is possible to generate Swift bindings to Java libraries using SwiftJava by using the `swift-java wrap-java` command. Required language/runtime versions: - **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration @@ -124,12 +124,12 @@ Please always use the gradle wrapper (`./gradlew`) to make sure to use the appro Sample apps are located in the `Samples/` directory, and they showcase full "roundtrip" usage of the library and/or tools. -#### JavaKit (Swift -> Java) +#### SwiftJava (Swift -> Java) To run a simple app showcasing a Swift process calling into a Java library you can run: ```bash -cd Samples/JavaKitSampleApp +cd Samples/SwiftJavaExtractFFMSampleApp ./ci-validate.sh # which is just `swift build` and a `java -cp ...` invocation of the compiled program ``` diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index d4c12083..997e680e 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -4,13 +4,13 @@ Summary of features supported by the swift-java interoperability libraries and t ## Overview -JavaKit supports both directions of interoperability, using Swift macros and source generation +SwiftJava supports both directions of interoperability, using Swift macros and source generation (via the `swift-java wrap-java` command). ### Java -> Swift -It is possible to use JavaKit macros and the `wrap-java` command to simplify implementing -Java `native` functions. JavaKit simplifies the type conversions +It is possible to use SwiftJava macros and the `wrap-java` command to simplify implementing +Java `native` functions. SwiftJava simplifies the type conversions > tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' > around the [7-minute mark](https://youtu.be/QSHO-GUGidA?si=vUXxphTeO-CHVZ3L&t=448). @@ -74,8 +74,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | | Optional return types: `func f() -> Int?`, `func g() -> MyClass?` | ❌ | ✅ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | -| Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | -| Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | +| Parameters: SwiftJava wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | +| Return values: SwiftJava wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | | Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ✅ * | ✅ * | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | From e187ad6f6ef72122218778b7b8af8bbd4c871a3e Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 1 Sep 2025 15:34:49 +0900 Subject: [PATCH 143/178] Rename reexport file to match reexported module (#385) --- .../{JavaRuntime+Reexport.swift => CSwiftJavaJNI+Reexport.swift} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/SwiftJava/{JavaRuntime+Reexport.swift => CSwiftJavaJNI+Reexport.swift} (100%) diff --git a/Sources/SwiftJava/JavaRuntime+Reexport.swift b/Sources/SwiftJava/CSwiftJavaJNI+Reexport.swift similarity index 100% rename from Sources/SwiftJava/JavaRuntime+Reexport.swift rename to Sources/SwiftJava/CSwiftJavaJNI+Reexport.swift From 748961d69abdde19ebe5463800fbe4cf6883c204 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 3 Sep 2025 20:03:06 +0900 Subject: [PATCH 144/178] Correct "running in ... mode" message This takes into account the settings file, so we should use the configured mode. Noticed when we printed "ffm mode" while we were actually in JNI mode --- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 29db6081..6fc5c2fd 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -103,7 +103,7 @@ extension SwiftJava.JExtractCommand { config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)" } - print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(self.mode)".bold) + print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.mode ?? .ffm)".bold) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) From 4b392128fca13aa014accda44543d21b71fcd65d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 10 Sep 2025 09:20:26 +0900 Subject: [PATCH 145/178] Start using Swift 6.1.3 in CI (#388) It contains a patch for swiftpm issues we've been facing during build https://forums.swift.org/t/announcing-swift-6-1-3/82022 --- .github/workflows/pull_request.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 0a2b6949..e0a3ee23 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['jammy'] jdk_vendor: ['corretto'] steps: @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2', 'nightly'] + swift_version: ['6.1.3', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -95,7 +95,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -115,7 +115,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2', 'nightly'] + swift_version: ['6.1.3', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -137,7 +137,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -157,7 +157,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2', 'nightly'] + swift_version: ['6.1.3', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] # no nightly testing on macOS + swift_version: ['6.1.3'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From 3d9433f95f6323b2707590b29474f12247ae842c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 10 Sep 2025 11:43:49 +0900 Subject: [PATCH 146/178] [SwiftJava] Fix Android build by making JNINativeInterface_ public (#387) --- Sources/SwiftJava/JavaEnvironment.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJava/JavaEnvironment.swift b/Sources/SwiftJava/JavaEnvironment.swift index a533a932..9506dbea 100644 --- a/Sources/SwiftJava/JavaEnvironment.swift +++ b/Sources/SwiftJava/JavaEnvironment.swift @@ -15,7 +15,7 @@ import CSwiftJavaJNI #if canImport(Android) -typealias JNINativeInterface_ = JNINativeInterface +public typealias JNINativeInterface_ = JNINativeInterface #endif extension UnsafeMutablePointer { From 94e87becdf38118760dd25e3b6cd6febf8d133f3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 22 Sep 2025 16:05:44 +0900 Subject: [PATCH 147/178] Fix error handling for JNI mode unsigned numbers Flip the assertion, the failure was in the wrong branch in JNI mode --- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 6fc5c2fd..ec7c0aeb 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -117,9 +117,9 @@ extension SwiftJava.JExtractCommand { if self.mode == .jni { switch self.unsignedNumbers { case .annotate: - throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") - case .wrapGuava: () // OK + case .wrapGuava: + throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") } } else if self.mode == .ffm { guard self.memoryManagementMode == .explicit else { From ebe04bbddd822179df2f179645c5cc132353d6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=C3=B3rio=20Gevartosky=20Torrezan?= Date: Sun, 28 Sep 2025 03:42:27 -0300 Subject: [PATCH 148/178] Fixing export JAVA_HOME command missing quote (#390) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52c61bb4..a1a18d79 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The use of JDK 24 is required to build the project, even though the libraries be When using sdkman the easiest way to export JAVA_HOME is to export the "current" used JDK's home, like this: ```bash -export JAVA_HOME="$(sdk home java current) +export JAVA_HOME="$(sdk home java current)" ``` ### Testing your changes From a1dc8484f0f89dd08210ada9362f6b294a091e31 Mon Sep 17 00:00:00 2001 From: Thomas Krajacic Date: Tue, 7 Oct 2025 14:48:10 +0200 Subject: [PATCH 149/178] Add note about installing JDK (#394) Co-authored-by: Konrad `ktoso` Malawski --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index a1a18d79..2cdeec9c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,23 @@ The primary purpose of this repository is to create an environment for collabora This project consists of different modules which have different Swift and Java runtime requirements. +On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using + +```bash +$ brew install openjdk +# and create a symlink into /Library/Java/JavaVirtualMachines +$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk + +# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines +$ brew install --cask corretto +``` + +or you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable + +```bash +$ export JAVA_HOME="$(sdk home java current)" +``` + ## SwiftJava macros SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. From a9d733236136d3756aca9a236e80c09b4a9df09a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 7 Oct 2025 21:48:38 +0900 Subject: [PATCH 150/178] Explain self-publishing swiftkit (#395) --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2cdeec9c..ff643c49 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,37 @@ This repository contains two approaches to Swift/Java interoperability. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. -## :construction: :construction: :construction: Early Development :construction: :construction: :construction: +## :construction: Early Development :construction: -**:construction: :construction: :construction: This is a *very early* prototype and everything is subject to change. :construction: :construction: :construction:** +**:construction: This is a *very early* prototype and everything is subject to change. :construction:** Parts of this project are incomplete, not fleshed out, and subject to change without any notice. The primary purpose of this repository is to create an environment for collaboration and joint exploration of the Swift/Java interoperability story. The project will transition to a more structured approach once key goals have been outlined. +### :construction: Self-publish support Java libraries (SwiftKit) + +While we work out how to provide the necessary support libraries for the Java side of Java code generated by `swift-java jextract`, +you will currently need to publish them locally and depend on them this way; + +To publish the libraries to your local maven repository (`$HOME/.m2`), you can run: + +``` +// in swift-java/ +./gradlew publishToMavenLocal +``` + +To consume these libraries in your Java project built using Gradle, you can then include the local repository in the repositories to resolve dependencies from: + +``` +repositories { + mavenLocal() + mavenCentral() +} +``` + +We anticipate simplifying this in the future. + ## Dependencies ### Required JDK versions From 92361949bf5809e48e6e22d053ef611cccfa09ee Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 7 Oct 2025 22:15:41 +0900 Subject: [PATCH 151/178] CI: Update macOS Swift from 6.1.3 to 6.2.0 (#396) * CI: Update macOS Swift from 6.1.3 to 6.2.0 * only change macOS in this PR --- .github/workflows/pull_request.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e0a3ee23..ffc42e3c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2.0'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -95,7 +95,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2.0'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -137,7 +137,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2.0'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3'] # no nightly testing on macOS + swift_version: ['6.2.0'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From 5cd11e5788a7677f339132a1dbd1fbf7f4e446e8 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 13 Oct 2025 06:19:14 +0200 Subject: [PATCH 152/178] Minimum changes for Android support (#401) --- Package.swift | 2 +- Sources/CSwiftJavaJNI/AndroidSupport.cpp | 98 ++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 Sources/CSwiftJavaJNI/AndroidSupport.cpp diff --git a/Package.swift b/Package.swift index 1c0e9481..52ec7ae7 100644 --- a/Package.swift +++ b/Package.swift @@ -362,7 +362,7 @@ let package = Package( name: "CSwiftJavaJNI", swiftSettings: [ .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) ] ), diff --git a/Sources/CSwiftJavaJNI/AndroidSupport.cpp b/Sources/CSwiftJavaJNI/AndroidSupport.cpp new file mode 100644 index 00000000..a223530a --- /dev/null +++ b/Sources/CSwiftJavaJNI/AndroidSupport.cpp @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifdef __ANDROID__ + +#include +#include +#include + +// these are not exported by the Android SDK + +extern "C" { + using JavaRuntime_GetDefaultJavaVMInitArgs_fn = jint (*)(void *vm_args); + using JavaRuntime_CreateJavaVM_fn = jint (*)(JavaVM **, JNIEnv **, void *); + using JavaRuntime_GetCreatedJavaVMs_fn = jint (*)(JavaVM **, jsize, jsize *); +} + +static JavaRuntime_GetDefaultJavaVMInitArgs_fn + JavaRuntime_GetDefaultJavaVMInitArgs; +static JavaRuntime_CreateJavaVM_fn JavaRuntime_CreateJavaVM; +static JavaRuntime_GetCreatedJavaVMs_fn JavaRuntime_GetCreatedJavaVMs; + +static void *JavaRuntime_dlhandle; + +__attribute__((constructor)) static void JavaRuntime_init(void) { + JavaRuntime_dlhandle = dlopen("libnativehelper.so", RTLD_NOW | RTLD_LOCAL); + if (JavaRuntime_dlhandle == nullptr) { + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "failed to open libnativehelper.so: %s", dlerror()); + return; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetDefaultJavaVMInitArgs")); + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetDefaultJavaVMInitArgs not found: %s", + dlerror()); + + JavaRuntime_CreateJavaVM = reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_CreateJavaVM")); + if (JavaRuntime_CreateJavaVM == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_CreateJavaVM not found: %s", dlerror()); + + JavaRuntime_GetCreatedJavaVMs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetCreatedJavaVMs")); + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetCreatedJavaVMs not found: %s", dlerror()); +} + +__attribute__((destructor)) static void JavaRuntime_deinit(void) { + if (JavaRuntime_dlhandle) { + dlclose(JavaRuntime_dlhandle); + JavaRuntime_dlhandle = nullptr; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = nullptr; + JavaRuntime_CreateJavaVM = nullptr; + JavaRuntime_GetCreatedJavaVMs = nullptr; +} + +jint JNI_GetDefaultJavaVMInitArgs(void *vm_args) { + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetDefaultJavaVMInitArgs)(vm_args); +} + +jint JNI_CreateJavaVM(JavaVM **vm, JNIEnv **env, void *vm_args) { + if (JavaRuntime_CreateJavaVM == nullptr) + return JNI_ERR; + + return (*JavaRuntime_CreateJavaVM)(vm, env, vm_args); +} + +jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) { + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetCreatedJavaVMs)(vmBuf, bufLen, nVMs); +} + +#endif From 791e734856e7155e9c9da7b5e9983b0ca12b4e3d Mon Sep 17 00:00:00 2001 From: Melissa Kilby Date: Wed, 15 Oct 2025 02:00:00 -0700 Subject: [PATCH 153/178] chore: restrict GitHub workflow permissions - future-proof (#399) --- .github/workflows/pull_request.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ffc42e3c..35cdd448 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,5 +1,8 @@ name: pull_request +permissions: + contents: read + on: pull_request: types: [opened, reopened, synchronize] From c77273e035a2358cbd6a5fd951b4af579bbb4c17 Mon Sep 17 00:00:00 2001 From: rayman-zhao <128336994+rayman-zhao@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:29:56 +0800 Subject: [PATCH 154/178] Fixed problems when build JavaKitSampleApp on Windows (#400) --- .../JavaCompilerPlugin.swift | 20 ++++++++++++------- Samples/JavaKitSampleApp/Package.swift | 5 ++--- .../JavaKitVM/JavaVirtualMachine.swift | 14 ++++++++++--- .../JavaKitVM/ThreadLocalStorage.swift | 2 +- .../Platforms/Subprocess+Windows.swift | 3 +-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index e12e13e8..f90150fd 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -30,11 +30,11 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // Note: Target doesn't have a directoryURL counterpart to directory, // so we cannot eliminate this deprecation warning. - let sourceDir = target.directory.string + let sourceDir = URL(filePath: target.directory.string) // The name of the configuration file SwiftJava.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 configFile = sourceDir.appending(path: "swift-java.config") let config: Configuration? if let configData = try? Data(contentsOf: configFile) { @@ -51,13 +51,14 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { } let sourceFilePath = sourceFileURL.path - guard sourceFilePath.starts(with: sourceDir) else { + let sourceDirPath = sourceDir.path + guard sourceFilePath.starts(with: sourceDirPath) 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))) + .appending(path: String(sourceFilePath.dropFirst(sourceDirPath.count))) .deletingPathExtension() .appendingPathExtension("class") } @@ -65,14 +66,19 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { let javaHome = URL(filePath: findJavaHome()) let javaClassFileURL = context.pluginWorkDirectoryURL .appending(path: "Java") + #if os(Windows) + let javac = "javac.exe" + #else + let javac = "javac" + #endif return [ .buildCommand( displayName: "Compiling \(javaFiles.count) Java files for target \(sourceModule.name) to \(javaClassFileURL)", executable: javaHome .appending(path: "bin") - .appending(path: "javac"), - arguments: javaFiles.map { $0.path(percentEncoded: false) } + [ - "-d", javaClassFileURL.path(), + .appending(path: javac), + arguments: javaFiles.map { $0.path } + [ + "-d", javaClassFileURL.path, "-parameters", // keep parameter names, which allows us to emit them in generated Swift decls ] + (config?.compilerVersionArgs ?? []), inputFiles: javaFiles, diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 32451e92..881b8d0b 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -35,9 +35,8 @@ let javaIncludePath = "\(javaHome)/include" let javaPlatformIncludePath = "\(javaIncludePath)/linux" #elseif os(macOS) let javaPlatformIncludePath = "\(javaIncludePath)/darwin" -#else - // TODO: Handle windows as well - #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#elseif os(Windows) + let javaPlatformIncludePath = "\(javaIncludePath)/win32)" #endif let package = Package( diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift index edbddbb7..7443039a 100644 --- a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift @@ -25,6 +25,14 @@ typealias JNIEnvPointer = UnsafeMutablePointer typealias JNIEnvPointer = UnsafeMutableRawPointer #endif +extension FileManager { +#if os(Windows) + static let pathSeparator = ";" +#else + static let pathSeparator = ":" +#endif +} + public final class JavaVirtualMachine: @unchecked Sendable { /// The JNI version that we depend on. static let jniVersion = JNI_VERSION_1_6 @@ -81,8 +89,8 @@ public final class JavaVirtualMachine: @unchecked Sendable { print("[warning][swift-java][JavaVirtualMachine] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr } } - let colonSeparatedClassPath = classpath.joined(separator: ":") - allVMOptions.append("-Djava.class.path=\(colonSeparatedClassPath)") + let pathSeparatedClassPath = classpath.joined(separator: FileManager.pathSeparator) + allVMOptions.append("-Djava.class.path=\(pathSeparatedClassPath)") } allVMOptions.append(contentsOf: vmOptions) @@ -237,7 +245,7 @@ extension JavaVirtualMachine { ignoreUnrecognized: Bool = false, replace: Bool = false ) throws -> JavaVirtualMachine { - precondition(!classpath.contains(where: { $0.contains(":") }), "Classpath element must not contain `:`! Split the path into elements! Was: \(classpath)") + precondition(!classpath.contains(where: { $0.contains(FileManager.pathSeparator) }), "Classpath element must not contain `\(FileManager.pathSeparator)`! Split the path into elements! Was: \(classpath)") return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in // If we already have a JavaVirtualMachine instance, return it. diff --git a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift index 7ea0b50a..037e328b 100644 --- a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift +++ b/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift @@ -56,7 +56,7 @@ package struct ThreadLocalStorage: ~Copyable { _key = 0 pthread_key_create(&_key, onThreadExit) #elseif canImport(WinSDK) - key = FlsAlloc(onThreadExit) + _key = FlsAlloc(onThreadExit) #endif } diff --git a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift index aaf53ea0..e84d1414 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift @@ -1213,8 +1213,7 @@ extension UInt8 { extension OutputProtocol { internal func output(from data: [UInt8]) throws -> OutputType { return try data.withUnsafeBytes { - let span = RawSpan(_unsafeBytes: $0) - return try self.output(from: span) + return try self.output(from: $0) } } } From 3173f82db004ad6068ffc4779ddc7a7936a12559 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 21 Oct 2025 11:46:45 +0200 Subject: [PATCH 155/178] Fix release build and enable benchmarks (#406) --- .github/workflows/pull_request.yml | 22 ++++++++++++++++++++++ Package.swift | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 35cdd448..f2a1db71 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -112,6 +112,28 @@ jobs: - name: Gradle compile JMH benchmarks run: ./gradlew compileJmh --info + benchmark-swift: + name: Benchmark (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + swift_version: ['6.2.0'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install jemalloc + run: apt-get update && apt-get install -y libjemalloc-dev + - name: Swift Benchmarks + run: swift package --package-path Benchmarks/ benchmark + test-swift: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest diff --git a/Package.swift b/Package.swift index 52ec7ae7..f6cc83e7 100644 --- a/Package.swift +++ b/Package.swift @@ -245,7 +245,8 @@ let package = Package( exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])), + .unsafeFlags(["-Xfrontend", "-sil-verify-none"], .when(configuration: .release)), // Workaround for https://github.com/swiftlang/swift/issues/84899 ], linkerSettings: [ .unsafeFlags( From 782af4738b2fb79755bb9c797a26d818d3e87040 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 22 Oct 2025 23:13:15 +0200 Subject: [PATCH 156/178] Change `SwiftKitCore` to Java 11 instead of 17 (#407) --- SwiftKitCore/build.gradle | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 82874e20..b61074a6 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -43,11 +43,13 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(24)) } - // Support Android 6+ (Java 7) - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType(JavaCompile).configureEach { + // Support Java 11 + options.release.set(11) } dependencies { From d006f9a785738a1287fd7f6e7a67e9732e635cad Mon Sep 17 00:00:00 2001 From: Nakaoka Rei Date: Fri, 24 Oct 2025 12:13:04 +1300 Subject: [PATCH 157/178] docs: fix typo in README.md (#408) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff643c49..48658747 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository contains two approaches to Swift/Java interoperability. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. -- The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. +- The `swift-java` tool which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. ## :construction: Early Development :construction: From f7485cc6e7864276061bc5b748a13fa8ca721c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Bukowiecki?= Date: Fri, 24 Oct 2025 01:13:26 +0200 Subject: [PATCH 158/178] Fix passed JExtractGenerationMode not being used by JExtractCommand. (#404) Co-authored-by: pelekon <13712101+pelekon@users.noreply.github.com> --- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index ec7c0aeb..c730cbf6 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -40,7 +40,7 @@ extension SwiftJava { @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 + var mode: JExtractGenerationMode? @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") var swiftModule: String @@ -86,6 +86,11 @@ extension SwiftJava.JExtractCommand { if let javaPackage { config.javaPackage = javaPackage } + if let mode { + config.mode = mode + } else if config.mode == nil { + config.mode = .ffm + } config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift From cfbcf6c62dd8a81b6f240cc771464beeec5138d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Bukowiecki?= Date: Fri, 24 Oct 2025 04:10:09 +0200 Subject: [PATCH 159/178] Properly extract usage of Data type from FoundationEssentials. (#405) Co-authored-by: pelekon <13712101+pelekon@users.noreply.github.com> --- .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 10 +-- ...MSwift2JavaGenerator+JavaTranslation.swift | 6 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 46 +++++++++- .../JNI/JNIJavaTypeTranslator.swift | 2 +- .../Swift2JavaTranslator.swift | 8 +- .../SwiftTypes/DependencyScanner.swift | 65 ++++++++++++++- .../SwiftTypes/ImportedSwiftModule.swift | 36 ++++++++ .../SwiftTypes/SwiftKnownModules.swift | 28 ++++++- .../SwiftTypes/SwiftKnownTypeDecls.swift | 8 +- .../SwiftTypes/SwiftKnownTypes.swift | 9 +- .../SwiftTypes/SwiftModuleSymbolTable.swift | 20 +++++ .../SwiftParsedModuleSymbolTableBuilder.swift | 13 ++- .../SwiftTypes/SwiftSymbolTable.swift | 21 +++-- .../JExtractSwiftTests/DataImportTests.swift | 83 ++++++++++++++----- 15 files changed, 303 insertions(+), 56 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/ImportedSwiftModule.swift diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 193b545f..2621dd8b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -125,7 +125,9 @@ extension SwiftKnownTypeDeclKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol, .optional: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol, + .essentialsData, .essentialsDataProtocol, .optional: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 5dfa5b71..7c8d7ab2 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -282,7 +282,7 @@ struct CdeclLowering { ) } - case .data: + case .foundationData, .essentialsData: break default: @@ -375,7 +375,7 @@ struct CdeclLowering { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { - case .data: + case .foundationData, .essentialsData: break case .unsafeRawPointer, .unsafeMutableRawPointer: throw LoweringError.unhandledType(.optional(wrappedType)) @@ -387,7 +387,7 @@ struct CdeclLowering { throw LoweringError.unhandledType(.optional(wrappedType)) case .void, .string: throw LoweringError.unhandledType(.optional(wrappedType)) - case .dataProtocol: + case .foundationDataProtocol, .essentialsDataProtocol: throw LoweringError.unhandledType(.optional(wrappedType)) default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. @@ -505,7 +505,7 @@ struct CdeclLowering { ]) ) - case .data: + case .foundationData, .essentialsData: break default: @@ -606,7 +606,7 @@ struct CdeclLowering { case .void: return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder) - case .data: + case .foundationData, .essentialsData: break case .string, .optional: diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 438393cb..5d38a0f9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -411,7 +411,7 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "SwiftRuntime.toCString", withArena: true) ) - case .data: + case .foundationData, .essentialsData: break default: @@ -515,7 +515,7 @@ extension FFMSwift2JavaGenerator { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { - case .data, .dataProtocol: + case .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol: break default: throw JavaTranslationError.unhandledType(.optional(swiftType)) @@ -658,7 +658,7 @@ extension FFMSwift2JavaGenerator { ) ) - case .data: + case .foundationData, .essentialsData: break case .unsafePointer, .unsafeMutablePointer: diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index f35973eb..e43ea4c5 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,11 +125,55 @@ extension FFMSwift2JavaGenerator { } func printSwiftThunkImports(_ printer: inout CodePrinter) { + let mainSymbolSourceModules = Set( + self.lookupContext.symbolTable.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) + ) + for module in self.lookupContext.symbolTable.importedModules.keys.sorted() { guard module != "Swift" else { continue } - printer.print("import \(module)") + + guard let alternativeModules = self.lookupContext.symbolTable.importedModules[module]?.alternativeModules else { + printer.print("import \(module)") + continue + } + + // Try to print only on main module from relation chain as it has every other module. + guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else { + if !alternativeModules.isMainSourceOfSymbols { + printer.print("import \(module)") + } + continue + } + + var importGroups: [String: [String]] = [:] + for name in alternativeModules.moduleNames { + guard let otherModule = self.lookupContext.symbolTable.importedModules[name] else { continue } + + let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName + importGroups[groupKey, default: []].append(otherModule.moduleName) + } + + for (index, group) in importGroups.keys.sorted().enumerated() { + if index > 0 && importGroups.keys.count > 1 { + printer.print("#elseif canImport(\(group))") + } else { + printer.print("#if canImport(\(group))") + } + + for groupModule in importGroups[group] ?? [] { + printer.print("import \(groupModule)") + } + } + + if (importGroups.keys.isEmpty) { + printer.print("import \(module)") + } else { + printer.print("#else") + printer.print("import \(module)") + printer.print("#endif") + } } printer.println() } diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 983396ac..0d1fc675 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -50,7 +50,7 @@ enum JNIJavaTypeTranslator { .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, - .optional, .data, .dataProtocol: + .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol: return nil } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index ccbbd274..9dcba340 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -108,10 +108,10 @@ extension Swift2JavaTranslator { visitor.visit(sourceFile: input.syntax) } - // If any API uses 'Foundation.Data', import 'Data' as if it's declared in - // this module. - if let dataDecl = self.symbolTable[.data] { - let dataProtocolDecl = self.symbolTable[.dataProtocol]! + // If any API uses 'Foundation.Data' or 'FoundationEssentials.Data', + // import 'Data' as if it's declared in this module. + if let dataDecl = self.symbolTable[.foundationData] ?? self.symbolTable[.essentialsData] { + let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])! if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift index 409c81a7..79abe981 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift @@ -15,15 +15,74 @@ import SwiftSyntax /// Scan importing modules. -func importingModuleNames(sourceFile: SourceFileSyntax) -> [String] { - var importingModuleNames: [String] = [] +func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftModule] { + var importingModuleNames: [ImportedSwiftModule] = [] for item in sourceFile.statements { if let importDecl = item.item.as(ImportDeclSyntax.self) { guard let moduleName = importDecl.path.first?.name.text else { continue } - importingModuleNames.append(moduleName) + importingModuleNames.append(ImportedSwiftModule(name: moduleName, availableWithModuleName: nil, alternativeModuleNames: [])) + } else if let ifConfigDecl = item.item.as(IfConfigDeclSyntax.self) { + importingModuleNames.append(contentsOf: modules(from: ifConfigDecl)) } } return importingModuleNames } + +private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftModule] { + guard + let firstClause = ifConfigDecl.clauses.first, + let calledExpression = firstClause.condition?.as(FunctionCallExprSyntax.self)?.calledExpression.as(DeclReferenceExprSyntax.self), + calledExpression.baseName.text == "canImport" else { + return [] + } + + var modules: [ImportedSwiftModule] = [] + modules.reserveCapacity(ifConfigDecl.clauses.count) + + for (index, clause) in ifConfigDecl.clauses.enumerated() { + let importedModuleNames = clause.elements?.as(CodeBlockItemListSyntax.self)? + .compactMap { CodeBlockItemSyntax($0) } + .compactMap { $0.item.as(ImportDeclSyntax.self) } + .compactMap { $0.path.first?.name.text } ?? [] + + let importModuleName: String? = if + let funcCallExpr = clause.condition?.as(FunctionCallExprSyntax.self), + let calledDeclReference = funcCallExpr.calledExpression.as(DeclReferenceExprSyntax.self), + calledDeclReference.baseName.text == "canImport", + let moduleNameSyntax = funcCallExpr.arguments.first?.expression.as(DeclReferenceExprSyntax.self) { + moduleNameSyntax.baseName.text + } else { + nil + } + + let clauseModules = importedModuleNames.map { + ImportedSwiftModule(name: $0, + availableWithModuleName: importModuleName, + alternativeModuleNames: []) + } + + // Assume single import from #else statement is fallback and use it as main source of symbols + if + clauseModules.count == 1 && + index == (ifConfigDecl.clauses.count - 1) && + clause.poundKeyword.tokenKind == .poundElse { + var fallbackModule: ImportedSwiftModule = clauseModules[0] + var moduleNames: [String] = [] + moduleNames.reserveCapacity(modules.count) + + for i in 0.. + var isMainSourceOfSymbols: Bool + + init(name: String, availableWithModuleName: String? = nil, alternativeModuleNames: Set = [], isMainSourceOfSymbols: Bool = false) { + self.name = name + self.availableWithModuleName = availableWithModuleName + self.alternativeModuleNames = alternativeModuleNames + self.isMainSourceOfSymbols = isMainSourceOfSymbols + } + + static func ==(lhs: Self, rhs: Self) -> Bool { + lhs.name == rhs.name + } + + func hash(into hasher: inout Hasher) { + hasher.combine(name) + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 938d0c4a..45d5df5a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -18,6 +18,7 @@ import SwiftSyntaxBuilder enum SwiftKnownModule: String { case swift = "Swift" case foundation = "Foundation" + case foundationEssentials = "FoundationEssentials" var name: String { return self.rawValue @@ -27,13 +28,15 @@ enum SwiftKnownModule: String { return switch self { case .swift: swiftSymbolTable case .foundation: foundationSymbolTable + case .foundationEssentials: foundationEssentialsSymbolTable } } var sourceFile: SourceFileSyntax { return switch self { case .swift: swiftSourceFile - case .foundation: foundationSourceFile + case .foundation: foundationEssentialsSourceFile + case .foundationEssentials: foundationEssentialsSourceFile } } } @@ -44,8 +47,21 @@ private var swiftSymbolTable: SwiftModuleSymbolTable { return builder.finalize() } +private var foundationEssentialsSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder( + moduleName: "FoundationEssentials", + requiredAvailablityOfModuleWithName: "FoundationEssentials", + alternativeModules: .init(isMainSourceOfSymbols: false, moduleNames: ["Foundation"]), + importedModules: ["Swift": swiftSymbolTable]) + builder.handle(sourceFile: foundationEssentialsSourceFile) + return builder.finalize() +} + private var foundationSymbolTable: SwiftModuleSymbolTable { - var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Foundation", importedModules: ["Swift": swiftSymbolTable]) + var builder = SwiftParsedModuleSymbolTableBuilder( + moduleName: "Foundation", + alternativeModules: .init(isMainSourceOfSymbols: true, moduleNames: ["FoundationEssentials"]), + importedModules: ["Swift": swiftSymbolTable]) builder.handle(sourceFile: foundationSourceFile) return builder.finalize() } @@ -87,7 +103,7 @@ private let swiftSourceFile: SourceFileSyntax = """ } """ -private let foundationSourceFile: SourceFileSyntax = """ +private let foundationEssentialsSourceFile: SourceFileSyntax = """ public protocol DataProtocol {} public struct Data: DataProtocol { @@ -96,3 +112,9 @@ private let foundationSourceFile: SourceFileSyntax = """ public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) } """ + +private var foundationSourceFile: SourceFileSyntax { + // On platforms other than Darwin, imports such as FoundationEssentials, FoundationNetworking, etc. are used, + // so this file should be created by combining the files of the aforementioned modules. + foundationEssentialsSourceFile +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 11ff25c4..809f2aa1 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -15,6 +15,7 @@ import SwiftSyntax enum SwiftKnownTypeDeclKind: String, Hashable { + // Swift case bool = "Swift.Bool" case int = "Swift.Int" case uint = "Swift.UInt" @@ -40,8 +41,11 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case void = "Swift.Void" case string = "Swift.String" - case dataProtocol = "Foundation.DataProtocol" - case data = "Foundation.Data" + // Foundation + case foundationDataProtocol = "Foundation.DataProtocol" + case essentialsDataProtocol = "FoundationEssentials.DataProtocol" + case foundationData = "Foundation.Data" + case essentialsData = "FoundationEssentials.Data" var moduleAndName: (module: String, name: String) { let qualified = self.rawValue diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index 8c70f7e2..25b1135a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -35,8 +35,10 @@ struct SwiftKnownTypes { var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } - var dataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.dataProtocol])) } - var data: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.data])) } + var foundationDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationDataProtocol])) } + var foundationData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationData])) } + var essentialsDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsDataProtocol])) } + var essentialsData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsData])) } func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( @@ -78,7 +80,8 @@ struct SwiftKnownTypes { /// given protocol kind. E.g. `String` for `StringProtocol` func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { switch knownProtocol { - case .dataProtocol: return self.data + case .foundationDataProtocol: return self.foundationData + case .essentialsDataProtocol: return self.essentialsData default: return nil } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift index f1bbaa12..6328045f 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift @@ -19,6 +19,12 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { /// The name of this module. let moduleName: String + /// The name of module required to be imported and checked via canImport statement. + let requiredAvailablityOfModuleWithName: String? + + /// Data about alternative modules which provides desired symbos e.g. FoundationEssentials is non-Darwin platform alternative for Foundation + let alternativeModules: AlternativeModuleNamesData? + /// The top-level nominal types, found by name. var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:] @@ -36,4 +42,18 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { nestedTypes[parent]?[name] } + + func isAlternative(for moduleName: String) -> Bool { + alternativeModules.flatMap { $0.moduleNames.contains(moduleName) } ?? false + } } + +extension SwiftModuleSymbolTable { + struct AlternativeModuleNamesData { + /// Flag indicating module should be used as source of symbols to avoid duplication of symbols. + let isMainSourceOfSymbols: Bool + + /// Names of modules which are alternative for currently checked module. + let moduleNames: Set + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 9b8ef236..8abb21f5 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -26,9 +26,18 @@ struct SwiftParsedModuleSymbolTableBuilder { /// Extension decls their extended type hasn't been resolved. var unresolvedExtensions: [ExtensionDeclSyntax] - init(moduleName: String, importedModules: [String: SwiftModuleSymbolTable], log: Logger? = nil) { + init( + moduleName: String, + requiredAvailablityOfModuleWithName: String? = nil, + alternativeModules: SwiftModuleSymbolTable.AlternativeModuleNamesData? = nil, + importedModules: [String: SwiftModuleSymbolTable], + log: Logger? = nil + ) { self.log = log - self.symbolTable = .init(moduleName: moduleName) + self.symbolTable = .init( + moduleName: moduleName, + requiredAvailablityOfModuleWithName: requiredAvailablityOfModuleWithName, + alternativeModules: alternativeModules) self.importedModules = importedModules self.unresolvedExtensions = [] } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 239d38c4..4271e297 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -42,6 +42,9 @@ package class SwiftSymbolTable { let parsedModule:SwiftModuleSymbolTable private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] + private var prioritySortedImportedModules: [SwiftModuleSymbolTable] { + importedModules.values.sorted(by: { ($0.alternativeModules?.isMainSourceOfSymbols ?? false) && $0.moduleName < $1.moduleName }) + } init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) { self.parsedModule = parsedModule @@ -58,18 +61,22 @@ extension SwiftSymbolTable { // Prepare imported modules. // FIXME: Support arbitrary dependencies. - var moduleNames: Set = [] + var modules: Set = [] for sourceFile in sourceFiles { - moduleNames.formUnion(importingModuleNames(sourceFile: sourceFile)) + modules.formUnion(importingModules(sourceFile: sourceFile)) } var importedModules: [String: SwiftModuleSymbolTable] = [:] importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable - for moduleName in moduleNames.sorted() { + for module in modules { + // We don't need duplicates of symbols, first known definition is enough to parse module + // e.g Data from FoundationEssentials and Foundation collide and lead to different results due to random order of keys in Swift's Dictionary + // guard module.isMainSourceOfSymbols || !importedModules.contains(where: { $0.value.isAlternative(for: String)}) else { continue } + if - importedModules[moduleName] == nil, - let knownModule = SwiftKnownModule(rawValue: moduleName) + importedModules[module.name] == nil, + let knownModule = SwiftKnownModule(rawValue: module.name) { - importedModules[moduleName] = knownModule.symbolTable + importedModules[module.name] = knownModule.symbolTable } } @@ -95,7 +102,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules.values { + for importedModule in prioritySortedImportedModules { if let result = importedModule.lookupTopLevelNominalType(name) { return result } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index cf4ed387..bad4174d 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -16,7 +16,14 @@ import JExtractSwiftLib import Testing final class DataImportTests { - let data_interfaceFile = + private static let ifConfigImport = """ + #if canImport(FoundationEssentials) + import FoundationEssentials + #else + import Foundation + #endif + """ + private static let foundationData_interfaceFile = """ import Foundation @@ -24,23 +31,53 @@ final class DataImportTests { public func returnData() -> Data """ - let dataProtocol_interfaceFile = + private static let foundationDataProtocol_interfaceFile = """ import Foundation public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ + private static let essentialsData_interfaceFile = + """ + import FoundationEssentials + + public func receiveData(dat: Data) + public func returnData() -> Data + """ - @Test("Import Data: Swift thunks") - func data_swiftThunk() throws { + private static let essentialsDataProtocol_interfaceFile = + """ + import FoundationEssentials + + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) + """ + private static let ifConfigData_interfaceFile = + """ + \(ifConfigImport) + + public func receiveData(dat: Data) + public func returnData() -> Data + """ + + private static let ifConfigDataProtocol_interfaceFile = + """ + \(ifConfigImport) + + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) + """ + + @Test("Import Data: Swift thunks", arguments: zip( + [Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) + func data_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( - input: data_interfaceFile, .ffm, .swift, + input: fileContent, .ffm, .swift, + detectChunkByInitialLines: 10, expectedChunks: [ - """ - import Foundation - """, + expectedImportChunk, """ @_cdecl("swiftjava_SwiftModule_receiveData_dat") public func swiftjava_SwiftModule_receiveData_dat(_ dat: UnsafeRawPointer) { @@ -87,11 +124,12 @@ final class DataImportTests { ) } - @Test("Import Data: JavaBindings") - func data_javaBindings() throws { - + @Test("Import Data: JavaBindings", arguments: [ + Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile + ]) + func data_javaBindings(fileContent: String) throws { try assertOutput( - input: data_interfaceFile, .ffm, .java, + input: fileContent, .ffm, .java, expectedChunks: [ """ /** @@ -333,14 +371,15 @@ final class DataImportTests { ) } - @Test("Import DataProtocol: Swift thunks") - func dataProtocol_swiftThunk() throws { + @Test("Import DataProtocol: Swift thunks", arguments: zip( + [Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) + func dataProtocol_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( - input: dataProtocol_interfaceFile, .ffm, .swift, + input: fileContent, .ffm, .swift, expectedChunks: [ - """ - import Foundation - """, + expectedImportChunk, """ @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2") public func swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(_ dat: UnsafeRawPointer, _ dat2: UnsafeRawPointer?) { @@ -356,11 +395,13 @@ final class DataImportTests { ) } - @Test("Import DataProtocol: JavaBindings") - func dataProtocol_javaBindings() throws { + @Test("Import DataProtocol: JavaBindings", arguments: [ + Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile + ]) + func dataProtocol_javaBindings(fileContent: String) throws { try assertOutput( - input: dataProtocol_interfaceFile, .ffm, .java, + input: fileContent, .ffm, .java, expectedChunks: [ """ /** From e072b2e35528fe364bdd6ab5527f4c7516f2fec6 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 24 Oct 2025 11:38:42 +0900 Subject: [PATCH 160/178] Revise project status and API stability notes in README Updated README to clarify project status and API stability. --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 48658747..7f25914d 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,9 @@ This repository contains two approaches to Swift/Java interoperability. ## :construction: Early Development :construction: -**:construction: This is a *very early* prototype and everything is subject to change. :construction:** +**:construction: This project is in early development, please keep this in mind as you try out the project and do provide feedback about any issues you encounter. :construction:** -Parts of this project are incomplete, not fleshed out, and subject to change without any notice. - -The primary purpose of this repository is to create an environment for collaboration and joint exploration of the Swift/Java interoperability story. The project will transition to a more structured approach once key goals have been outlined. +There is no guarantee about API stability of this package, neither in the Java or Swift parts, until the project releases a stable 1.0 release; APIs may change without prior notice. ### :construction: Self-publish support Java libraries (SwiftKit) @@ -66,7 +64,7 @@ SwiftJava is a Swift library offering macros which simplify writing JNI code "by It is possible to generate Swift bindings to Java libraries using SwiftJava by using the `swift-java wrap-java` command. Required language/runtime versions: -- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration +- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integratio - **Swift 6.0.x**, because the library uses modern Swift macros **swift-java jextract** @@ -83,8 +81,7 @@ This is the primary way we envision calling Swift code from server-side Java lib Required language/runtime versions: - **Swift 6.1**, because of dependence on rich swift interface files -- **JDK 24+** - - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-24. +- **JDK 25+**, the most recent LTS release which includes the stable Foreign Function and Memory APIs. ## swift-java jextract --mode=jni From d7e195949c3bb625c6c6e2944dc7ce3f88d96214 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 24 Oct 2025 15:17:57 +0900 Subject: [PATCH 161/178] Update JDK from 24 to 25 (#409) --- .github/actions/prepare_env/action.yml | 10 +++++----- ...d-logic.java-common-conventions.gradle.kts | 2 +- README.md | 13 ++++++------ Samples/SwiftAndJavaJarSampleLib/build.gradle | 17 ++++++++-------- .../SwiftAndJavaJarSampleLib/ci-validate.sh | 14 ++++++------- .../SwiftJavaExtractFFMSampleApp/build.gradle | 17 ++++++++-------- .../SwiftJavaExtractJNISampleApp/build.gradle | 17 ++++++++-------- SwiftKitCore/build.gradle | 3 ++- SwiftKitFFM/build.gradle | 3 ++- docker/Dockerfile | 2 +- docker/install_jdk.sh | 20 +++++++++---------- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 13 files changed, 64 insertions(+), 58 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 3fdd5d4b..90854db0 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -12,7 +12,7 @@ runs: with: distribution: ${{ matrix.jdk_vendor }} java-version: | - 24 + 25 17 cache: 'gradle' - name: Set JAVA_HOME_{N} @@ -23,10 +23,10 @@ runs: elif [[ -n "$JAVA_HOME_21_ARM64" ]]; then echo "JAVA_HOME_21=$JAVA_HOME_21_ARM64" >> $GITHUB_ENV fi - if [[ -n "$JAVA_HOME_24_X64" ]]; then - echo "JAVA_HOME_24=$JAVA_HOME_24_X64" >> $GITHUB_ENV - elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then - echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV + if [[ -n "$JAVA_HOME_25_X64" ]]; then + echo "JAVA_HOME_25=$JAVA_HOME_25_X64" >> $GITHUB_ENV + elif [[ -n "$JAVA_HOME_25_ARM64" ]]; then + echo "JAVA_HOME_25=$JAVA_HOME_25_ARM64" >> $GITHUB_ENV fi # - name: Check Java environment # shell: bash 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 df0a6633..1f2df6e5 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(24) + languageVersion = JavaLanguageVersion.of(25) } } diff --git a/README.md b/README.md index 7f25914d..da56781c 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,9 @@ This does require the use of the relatively recent [JEP-454: Foreign Function & This is the primary way we envision calling Swift code from server-side Java libraries and applications. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files -- **JDK 25+**, the most recent LTS release which includes the stable Foreign Function and Memory APIs. +- **Swift 6.1**, because of dependence on rich swift interface files +- **JDK 25+** + - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-25. ## swift-java jextract --mode=jni @@ -101,7 +102,7 @@ This project contains multiple builds, living side by side together. You will need to have: - Swift (6.1.x+) -- Java (24+ for FFM, even though we support lower JDK targets) +- Java (25+ for FFM, even though we support lower JDK targets) - Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) ### Preparing your environment @@ -120,12 +121,12 @@ however any recent enough Java distribution should work correctly. You can use s # Install sdkman from: https://sdkman.io curl -s "https://get.sdkman.io" | bash sdk install java 17.0.15-amzn -sdk install java 24.0.1-amzn +sdk install java 25.0.1-amzn -sdk use java 24.0.1-amzn +sdk use java 25.0.1-amzn ``` -The use of JDK 24 is required to build the project, even though the libraries being published may target lower Java versions. +The use of JDK 25 is required to build the project, even though the libraries being published may target lower Java versions. ❗️ Please make sure to `export JAVA_HOME` such that swift-java can find the necessary java libraries! When using sdkman the easiest way to export JAVA_HOME is to export the "current" used JDK's home, like this: diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index 96b25e22..3ad59fe5 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -38,7 +38,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -46,6 +46,7 @@ dependencies { implementation(project(':SwiftKitCore')) implementation(project(':SwiftKitFFM')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } @@ -54,16 +55,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh index 4fde0ef0..2daddc61 100755 --- a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -10,19 +10,19 @@ SWIFT_VERSION="$(swift -version | awk '/Swift version/ { print $3 }')" # This is how env variables are set by setup-java if [ "$(uname -m)" = 'arm64' ]; then ARCH=ARM64 - JAVAC="${JAVA_HOME_24_ARM64}/bin/javac" - JAVA="${JAVA_HOME_24_ARM64}/bin/java" + JAVAC="${JAVA_HOME_25_ARM64}/bin/javac" + JAVA="${JAVA_HOME_25_ARM64}/bin/java" else ARCH=X64 - JAVAC="${JAVA_HOME_24_X64}/bin/javac" - JAVA="${JAVA_HOME_24_X64}/bin/java" + JAVAC="${JAVA_HOME_25_X64}/bin/javac" + JAVA="${JAVA_HOME_25_X64}/bin/java" fi -if [ -n "$JAVA_HOME_24_$ARCH" ]; then - export JAVA_HOME="$JAVA_HOME_24_$ARCH" +if [ -n "$JAVA_HOME_25_$ARCH" ]; then + export JAVA_HOME="$JAVA_HOME_25_$ARCH" elif [ "$(uname -s)" = 'Linux' ] then - 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 + export PATH="${PATH}:/usr/lib/jvm/jdk-25/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 diff --git a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle index 048c984d..0200f234 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle +++ b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle @@ -31,7 +31,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -39,16 +39,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> @@ -150,6 +150,7 @@ dependencies { implementation(project(':SwiftKitCore')) implementation(project(':SwiftKitFFM')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index 54bd725a..b1aa8490 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -32,7 +32,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -40,16 +40,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> @@ -150,6 +150,7 @@ tasks.clean { dependencies { implementation(project(':SwiftKitCore')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index b61074a6..9e4891dc 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -43,7 +43,7 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -53,6 +53,7 @@ tasks.withType(JavaCompile).configureEach { } dependencies { + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index d818586d..c04b1017 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -42,13 +42,14 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } dependencies { implementation(project(':SwiftKitCore')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/docker/Dockerfile b/docker/Dockerfile index c3568b54..1109dab3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -20,7 +20,7 @@ ENV LANGUAGE=en_US.UTF-8 # JDK dependency RUN curl -s "https://get.sdkman.io" | bash -RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 24.0.1-amzn" +RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 25.0.1-amzn" RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ tar zxf swiftly-$(uname -m).tar.gz && \ diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8746e3ab..b69545f9 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -14,7 +14,7 @@ ##===----------------------------------------------------------------------===## set -euo pipefail -# We need JDK 24 because that's the supported version with latest FFM +# We need JDK 25 because that's the supported version with latest FFM # However, we also need JDK 23 at most because Gradle does not support 24. # Supported JDKs: corretto @@ -36,9 +36,9 @@ download_and_install_jdk() { if [ "$JDK_VENDOR" = 'corretto' ]; then if [ "$(uname -m)" = 'aarch64' ]; then case "$jdk_version" in - "24") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-aarch64-linux-jdk.tar.gz" - expected_md5="3b543f4e971350b73d0ab6d8174cc030" + "25") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-25-aarch64-linux-jdk.tar.gz" + expected_md5="37588d5d2a24b26525b9c563ad65cc77" ;; *) echo "Unsupported JDK version: '$jdk_version'" @@ -47,9 +47,9 @@ download_and_install_jdk() { esac else case "$jdk_version" in - "24") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-x64-linux-jdk.tar.gz" - expected_md5="130885ded3cbfc712fbe9f7dace45a52" + "25") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-25-x64-linux-jdk.tar.gz" + expected_md5="7e56b1a9d71637ce4dc4047b23d0453e" ;; *) echo "Unsupported JDK version: '$jdk_version'" @@ -94,12 +94,12 @@ download_and_install_jdk() { cd "$HOME" } -# Usage: Install JDK 24 -download_and_install_jdk "24" +# Usage: Install JDK 25 +download_and_install_jdk "25" ls -la /usr/lib/jvm/ cd /usr/lib/jvm/ -ln -s jdk-24 default-jdk +ln -s jdk-25 default-jdk find . | grep java | grep bin echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 679a1a1f..182db452 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64 \ No newline at end of file +org.gradle.java.installations.fromEnv=JAVA_HOME_25,JAVA_HOME_25_X64,JAVA_HOME_25_ARM64 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68d..2e111328 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 6168c628759ca85088d84fa0ae2c1e98d0ca8985 Mon Sep 17 00:00:00 2001 From: Dave Lester <18080+davelester@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:55:21 -0700 Subject: [PATCH 162/178] Updates swift-java project README (#411) * Updates project README.md to clarify its current status, make the language around self-publication of supporting libraries more accessible, and remove the construction emoji that previously appeared. * Update README.md Co-authored-by: Honza Dvorsky --------- Co-authored-by: Dave Lester Co-authored-by: Konrad `ktoso` Malawski Co-authored-by: Honza Dvorsky --- README.md | 57 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index da56781c..2688f288 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,32 @@ This repository contains two approaches to Swift/Java interoperability. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. -## :construction: Early Development :construction: +## Dependencies + +### Required JDK versions + +Note that this project consists of multiple modules which currently have different Swift and Java runtime requirements. + +You'll need to install the necessary JDK version locally. On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using: + +```bash +$ brew install openjdk +# and create a symlink into /Library/Java/JavaVirtualMachines +$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk -**:construction: This project is in early development, please keep this in mind as you try out the project and do provide feedback about any issues you encounter. :construction:** +# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines +$ brew install --cask corretto +``` -There is no guarantee about API stability of this package, neither in the Java or Swift parts, until the project releases a stable 1.0 release; APIs may change without prior notice. +Alternatively, you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable: + +```bash +$ export JAVA_HOME="$(sdk home java current)" +``` -### :construction: Self-publish support Java libraries (SwiftKit) +## Self-publish supporting Java libraries -While we work out how to provide the necessary support libraries for the Java side of Java code generated by `swift-java jextract`, -you will currently need to publish them locally and depend on them this way; +Swift-java relies on supporting libraries that are under active development and not yet published to Maven Central. To use the project, you'll need to self-publish these libraries locally so your Java project can depend on them. To publish the libraries to your local maven repository (`$HOME/.m2`), you can run: @@ -34,29 +50,6 @@ repositories { We anticipate simplifying this in the future. -## Dependencies - -### Required JDK versions - -This project consists of different modules which have different Swift and Java runtime requirements. - -On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using - -```bash -$ brew install openjdk -# and create a symlink into /Library/Java/JavaVirtualMachines -$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk - -# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines -$ brew install --cask corretto -``` - -or you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable - -```bash -$ export JAVA_HOME="$(sdk home java current)" -``` - ## SwiftJava macros SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. @@ -231,3 +224,9 @@ xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc # Monitoring /Users/ktoso/code/swift-java/Sources/SwiftJavaDocumentation/Documentation.docc for changes... ``` + +## Project Status + +**This project is under active development. We welcome feedback about any issues you encounter.** + +There is no guarantee about API stability until the project reaches a 1.0 release. \ No newline at end of file From 89b2bcc8b66a17fd94779f0b9d6ceb2df32e120b Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sat, 25 Oct 2025 07:19:45 +0900 Subject: [PATCH 163/178] jextract: generate one output swift file per input file (#403) --- .../JExtractSwiftPlugin.swift | 8 +- .../MySwiftLibrary/MultiplePublicTypes.swift | 26 ++++++ .../ci-validate.sh | 2 + .../MultipleTypesFromSingleFileTest.java | 36 +++++++++ .../MySwiftLibrary/MultiplePublicTypes.swift | 26 ++++++ .../SwiftJavaExtractJNISampleApp/build.gradle | 3 +- .../ci-validate.sh | 2 + .../MultipleTypesFromSingleFileTest.java | 36 +++++++++ Sources/JExtractSwiftLib/CodePrinter.swift | 34 ++++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 47 ++++++++--- .../FFM/FFMSwift2JavaGenerator.swift | 20 ++--- Sources/JExtractSwiftLib/ImportedDecls.swift | 8 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 47 ++++++++--- .../JNI/JNISwift2JavaGenerator.swift | 6 +- .../Swift2JavaTranslator.swift | 29 +++---- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 80 +++++++++++++------ .../SwiftTypes/SwiftFunctionSignature.swift | 2 +- .../SwiftTypes/SwiftKnownModules.swift | 6 +- .../SwiftNominalTypeDeclaration.swift | 27 ++++++- .../SwiftParsedModuleSymbolTableBuilder.swift | 26 +++--- .../SwiftTypes/SwiftSymbolTable.swift | 13 +-- .../SwiftTypes/SwiftTypeLookupContext.swift | 25 +++--- .../JavaKitVM/JavaVirtualMachine.swift | 2 +- .../Asserts/TextAssertions.swift | 2 +- .../FuncCallbackImportTests.swift | 6 +- .../FunctionDescriptorImportTests.swift | 4 +- .../MethodImportTests.swift | 18 ++--- .../SwiftSymbolTableTests.swift | 5 +- 28 files changed, 399 insertions(+), 147 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 49ed3c4d..8d0be455 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -93,6 +93,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { $0.pathExtension == "swift" } + // Output Swift files are just Java filename based converted to Swift files one-to-one var outputSwiftFiles: [URL] = swiftFiles.compactMap { sourceFileURL in guard sourceFileURL.isFileURL else { return nil as URL? @@ -102,7 +103,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { guard sourceFilePath.starts(with: sourceDir) else { fatalError("Could not get relative path for source file \(sourceFilePath)") } - var outputURL = outputSwiftDirectory + let outputURL = outputSwiftDirectory .appending(path: String(sourceFilePath.dropFirst(sourceDir.count).dropLast(sourceFileURL.lastPathComponent.count + 1))) let inputFileName = sourceFileURL.deletingPathExtension().lastPathComponent @@ -116,11 +117,12 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // If the module uses 'Data' type, the thunk file is emitted as if 'Data' is declared // in that module. Declare the thunk file as the output. - // FIXME: Make this conditional. outputSwiftFiles += [ - outputSwiftDirectory.appending(path: "Data+SwiftJava.swift") + outputSwiftDirectory.appending(path: "Foundation+SwiftJava.swift") ] + print("[swift-java-plugin] Output swift files:\n - \(outputSwiftFiles.map({$0.absoluteString}).joined(separator: "\n - "))") + return [ .buildCommand( displayName: "Generate Java wrappers for Swift types", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift new file mode 100644 index 00000000..234cb357 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 file exists to exercise the swiftpm plugin generating separate output Java files +// for the public types; because Java public types must be in a file with the same name as the type. + +public struct PublicTypeOne { + public init() {} + public func test() {} +} + +public struct PublicTypeTwo { + public init() {} + public func test() {} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh index c7a68d22..8758bbee 100755 --- a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh @@ -3,5 +3,7 @@ set -x set -e +swift build # as a workaround for building swift build from within gradle having issues on CI sometimes + ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java new file mode 100644 index 00000000..d3cd791c --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.*; +import org.swift.swiftkit.ffm.*; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class MultipleTypesFromSingleFileTest { + + @Test + void bothTypesMustHaveBeenGenerated() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + PublicTypeOne.init(arena); + PublicTypeTwo.init(arena); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift new file mode 100644 index 00000000..b7a02d78 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 file exists to exercise the swiftpm plugin generating separate output Java files +// for the public types; because Java public types must be in a file with the same name as the type. + +public struct PublicTypeOne { + public init() {} + public func test() {} +} + +public struct PublicTypeTwo { + public init() {} + public func test() {} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index b1aa8490..d92c96fb 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,7 +102,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // TODO: -v for debugging build issues... + args("build", "-v") // 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", diff --git a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh index c7a68d22..8758bbee 100755 --- a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh @@ -3,5 +3,7 @@ set -x set -e +swift build # as a workaround for building swift build from within gradle having issues on CI sometimes + ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java new file mode 100644 index 00000000..ae02b872 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class MultipleTypesFromSingleFileTest { + + @Test + void bothTypesMustHaveBeenGenerated() { + try (var arena = SwiftArena.ofConfined()) { + PublicTypeOne.init(arena); + PublicTypeTwo.init(arena); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index 8c22fbee..c5f04d51 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -216,14 +216,28 @@ extension CodePrinter { /// - Returns: the output path of the generated file, if any (i.e. not in accumulate in memory mode) package mutating func writeContents( - outputDirectory: String, + outputDirectory _outputDirectory: String, javaPackagePath: String?, - filename: String + filename _filename: String ) throws -> URL? { + + // We handle 'filename' that has a path, since that simplifies passing paths from root output directory enourmously. + // This just moves the directory parts into the output directory part in order for us to create the sub-directories. + let outputDirectory: String + let filename: String + if _filename.contains(PATH_SEPARATOR) { + let parts = _filename.split(separator: PATH_SEPARATOR) + outputDirectory = _outputDirectory.appending(PATH_SEPARATOR).appending(parts.dropLast().joined(separator: PATH_SEPARATOR)) + filename = "\(parts.last!)" + } else { + outputDirectory = _outputDirectory + filename = _filename + } + guard self.mode != .accumulateAll else { // if we're accumulating everything, we don't want to finalize/flush any contents // let's mark that this is where a write would have happened though: - print("// ^^^^ Contents of: \(outputDirectory)/\(filename)") + print("// ^^^^ Contents of: \(outputDirectory)\(PATH_SEPARATOR)\(filename)") return nil } @@ -233,7 +247,7 @@ extension CodePrinter { "// ==== ---------------------------------------------------------------------------------------------------" ) if let javaPackagePath { - print("// \(javaPackagePath)/\(filename)") + print("// \(javaPackagePath)\(PATH_SEPARATOR)\(filename)") } else { print("// \(filename)") } @@ -242,9 +256,15 @@ extension CodePrinter { } let targetDirectory = [outputDirectory, javaPackagePath].compactMap { $0 }.joined(separator: PATH_SEPARATOR) - log.trace("Prepare target directory: \(targetDirectory)") - try FileManager.default.createDirectory( - atPath: targetDirectory, withIntermediateDirectories: true) + log.debug("Prepare target directory: '\(targetDirectory)' for file \(filename.bold)") + do { + try FileManager.default.createDirectory( + atPath: targetDirectory, withIntermediateDirectories: true) + } catch { + // log and throw since it can be confusing what the reason for failing the write was otherwise + log.warning("Failed to create directory: \(targetDirectory)") + throw error + } let outputPath = Foundation.URL(fileURLWithPath: targetDirectory).appendingPathComponent(filename) try contents.write( diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index e43ea4c5..5ef266c8 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -22,8 +22,16 @@ extension FFMSwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { + let pendingFileCount = self.expectedOutputSwiftFiles.count + guard pendingFileCount > 0 else { + return // no need to write any empty files, yay + } + + print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") + for expectedFileName in self.expectedOutputSwiftFiles { - log.trace("Write empty file: \(expectedFileName) ...") + log.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") + var printer = CodePrinter() printer.print("// Empty file generated on purpose") @@ -46,7 +54,7 @@ extension FFMSwift2JavaGenerator { outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: moduleFilename) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + log.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") self.expectedOutputSwiftFiles.remove(moduleFilename) } } catch { @@ -54,24 +62,41 @@ extension FFMSwift2JavaGenerator { } // === 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" - log.debug("Printing contents: \(filename)") + // We have to write all types to their corresponding output file that matches the file they were declared in, + // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. + for group: (key: String, value: [Dictionary.Element]) in Dictionary(grouping: self.analysis.importedTypes, by: { $0.value.sourceFilePath }) { + log.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") + + let importedTypesForThisFile = group.value + .map(\.value) + .sorted(by: { $0.qualifiedName < $1.qualifiedName }) + + let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift" + let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift") + + for ty in importedTypesForThisFile { + log.info("Printing Swift thunks for type: \(ty.qualifiedName.bold)") + printer.printSeparator("Thunks for \(ty.qualifiedName)") + + do { + try printSwiftThunkSources(&printer, ty: ty) + } catch { + log.warning("Failed to print to Swift thunks for type'\(ty.qualifiedName)' to '\(filename)', error: \(error)") + } + + } + log.warning("Write Swift thunks file: \(filename.bold)") do { - try printSwiftThunkSources(&printer, ty: ty) - if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: filename) { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + log.info("Done writing Swift thunks to: \(outputFile.absoluteString)") self.expectedOutputSwiftFiles.remove(filename) } } catch { - log.warning("Failed to write to Swift thunks: \(filename)") + log.warning("Failed to write to Swift thunks: \(filename), error: \(error)") } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index c9a5028b..f9ba88b9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -60,16 +60,14 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { // 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 { + guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { return nil } return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - - // FIXME: Can we avoid this? - self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") + self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } @@ -77,16 +75,12 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { func generate() throws { try writeSwiftThunkSources() - print("[swift-java] Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") + log.info("Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") try writeExportedJavaSources() - print("[swift-java] Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/") + log.info("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() - } + try writeSwiftExpectedEmptySources() } } @@ -134,7 +128,7 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename ) { - print("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") + log.info("Generated: \((ty.swiftNominal.name.bold + ".java").bold) (at \(outputFile.absoluteString))") } } @@ -148,7 +142,7 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename) { - print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") + log.info("Generated: \((self.swiftModuleName + ".java").bold) (at \(outputFile.absoluteString))") } } } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 7a071d9a..1f0d3efc 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -27,9 +27,15 @@ package enum SwiftAPIKind { /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been /// imported and is being translated into Java. -package class ImportedNominalType: ImportedDecl { +package final class ImportedNominalType: ImportedDecl { let swiftNominal: SwiftNominalTypeDeclaration + // The short path from module root to the file in which this nominal was originally declared. + // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. + package var sourceFilePath: String { + self.swiftNominal.sourceFilePath + } + package var initializers: [ImportedFunc] = [] package var methods: [ImportedFunc] = [] package var variables: [ImportedFunc] = [] diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3848ceac..1d4f81dc 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -26,8 +26,16 @@ extension JNISwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { + let pendingFileCount = self.expectedOutputSwiftFiles.count + guard pendingFileCount > 0 else { + return // no need to write any empty files, yay + } + + print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") + for expectedFileName in self.expectedOutputSwiftFiles { - logger.trace("Write empty file: \(expectedFileName) ...") + logger.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") + var printer = CodePrinter() printer.print("// Empty file generated on purpose") @@ -52,27 +60,46 @@ extension JNISwift2JavaGenerator { javaPackagePath: nil, filename: moduleFilename ) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + logger.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") self.expectedOutputSwiftFiles.remove(moduleFilename) } - 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" - logger.debug("Printing contents: \(filename)") + // === All types + // We have to write all types to their corresponding output file that matches the file they were declared in, + // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. + for group: (key: String, value: [Dictionary.Element]) in Dictionary(grouping: self.analysis.importedTypes, by: { $0.value.sourceFilePath }) { + logger.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") - do { - try printNominalTypeThunks(&printer, ty) + let importedTypesForThisFile = group.value + .map(\.value) + .sorted(by: { $0.qualifiedName < $1.qualifiedName }) + let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift" + let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift") + + for ty in importedTypesForThisFile { + logger.info("Printing Swift thunks for type: \(ty.qualifiedName.bold)") + printer.printSeparator("Thunks for \(ty.qualifiedName)") + + do { + try printNominalTypeThunks(&printer, ty) + } catch { + logger.warning("Failed to print to Swift thunks for type'\(ty.qualifiedName)' to '\(filename)', error: \(error)") + } + + } + + logger.warning("Write Swift thunks file: \(filename.bold)") + do { if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: filename) { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + logger.info("Done writing Swift thunks to: \(outputFile.absoluteString)") self.expectedOutputSwiftFiles.remove(filename) } } catch { - logger.warning("Failed to write to Swift thunks: \(filename)") + logger.warning("Failed to write to Swift thunks: \(filename), error: \(error)") } } } catch { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index a677bcde..3b84cfb9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -67,16 +67,14 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { // 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 { + guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { return nil } return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - - // FIXME: Can we avoid this? - self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") + self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 9dcba340..7de792c0 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -32,12 +32,7 @@ public final class Swift2JavaTranslator { // ==== Input - struct Input { - let filePath: String - let syntax: SourceFileSyntax - } - - var inputs: [Input] = [] + var inputs: [SwiftJavaInputFile] = [] /// A list of used Swift class names that live in dependencies, e.g. `JavaInteger` package var dependenciesClasses: [String] = [] @@ -85,15 +80,12 @@ extension Swift2JavaTranslator { package func add(filePath: String, text: String) { log.trace("Adding: \(filePath)") let sourceFileSyntax = Parser.parse(source: text) - self.inputs.append(Input(filePath: filePath, syntax: sourceFileSyntax)) + self.inputs.append(SwiftJavaInputFile(syntax: sourceFileSyntax, path: filePath)) } /// Convenient method for analyzing single file. - package func analyze( - file: String, - text: String - ) throws { - self.add(filePath: file, text: text) + package func analyze(path: String, text: String) throws { + self.add(filePath: path, text: text) try self.analyze() } @@ -104,8 +96,8 @@ extension Swift2JavaTranslator { let visitor = Swift2JavaVisitor(translator: self) for input in self.inputs { - log.trace("Analyzing \(input.filePath)") - visitor.visit(sourceFile: input.syntax) + log.trace("Analyzing \(input.path)") + visitor.visit(inputFile: input) } // If any API uses 'Foundation.Data' or 'FoundationEssentials.Data', @@ -113,7 +105,7 @@ extension Swift2JavaTranslator { if let dataDecl = self.symbolTable[.foundationData] ?? self.symbolTable[.essentialsData] { let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])! if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { - visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) + visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATA.swift") } } } @@ -123,7 +115,7 @@ extension Swift2JavaTranslator { let symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, - inputs.map({ $0.syntax }) + [dependenciesSource], + inputs + [dependenciesSource], log: self.log ) self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) @@ -184,13 +176,14 @@ extension Swift2JavaTranslator { } /// Returns a source file that contains all the available dependency classes. - private func buildDependencyClassesSourceFile() -> SourceFileSyntax { + private func buildDependencyClassesSourceFile() -> SwiftJavaInputFile { let contents = self.dependenciesClasses.map { "public class \($0) {}" } .joined(separator: "\n") - return SourceFileSyntax(stringLiteral: contents) + let syntax = SourceFileSyntax(stringLiteral: contents) + return SwiftJavaInputFile(syntax: syntax, path: "FakeDependencyClassesSourceFile.swift") } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 13185a5c..247b2662 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -29,41 +29,42 @@ final class Swift2JavaVisitor { var log: Logger { translator.log } - func visit(sourceFile node: SourceFileSyntax) { + func visit(inputFile: SwiftJavaInputFile) { + let node = inputFile.syntax for codeItem in node.statements { if let declNode = codeItem.item.as(DeclSyntax.self) { - self.visit(decl: declNode, in: nil) + self.visit(decl: declNode, in: nil, sourceFilePath: inputFile.path) } } } - func visit(decl node: DeclSyntax, in parent: ImportedNominalType?) { + func visit(decl node: DeclSyntax, in parent: ImportedNominalType?, sourceFilePath: String) { switch node.as(DeclSyntaxEnum.self) { case .actorDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .classDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .structDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .enumDecl(let node): - self.visit(enumDecl: node, in: parent) + self.visit(enumDecl: node, in: parent, sourceFilePath: sourceFilePath) case .protocolDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .extensionDecl(let node): - self.visit(extensionDecl: node, in: parent) + self.visit(extensionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .typeAliasDecl: break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338 case .associatedTypeDecl: - break // TODO: Implement + break // TODO: Implement associated types case .initializerDecl(let node): self.visit(initializerDecl: node, in: parent) case .functionDecl(let node): - self.visit(functionDecl: node, in: parent) + self.visit(functionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .variableDecl(let node): - self.visit(variableDecl: node, in: parent) + self.visit(variableDecl: node, in: parent, sourceFilePath: sourceFilePath) case .subscriptDecl: - // TODO: Implement + // TODO: Implement subscripts break case .enumCaseDecl(let node): self.visit(enumCaseDecl: node, in: parent) @@ -75,23 +76,32 @@ final class Swift2JavaVisitor { func visit( nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax, - in parent: ImportedNominalType? + in parent: ImportedNominalType?, + sourceFilePath: String ) { guard let importedNominalType = translator.importedNominalType(node, parent: parent) else { return } for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType) + self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) } } - func visit(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { - self.visit(nominalDecl: node, in: parent) + func visit( + enumDecl node: EnumDeclSyntax, + in parent: ImportedNominalType?, + sourceFilePath: String + ) { + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) self.synthesizeRawRepresentableConformance(enumDecl: node, in: parent) } - func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { + func visit( + extensionDecl node: ExtensionDeclSyntax, + in parent: ImportedNominalType?, + sourceFilePath: String + ) { guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore return @@ -100,11 +110,15 @@ final class Swift2JavaVisitor { return } for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType) + self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) } } - func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + functionDecl node: FunctionDeclSyntax, + in typeContext: ImportedNominalType?, + sourceFilePath: String + ) { guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -139,7 +153,10 @@ final class Swift2JavaVisitor { } } - func visit(enumCaseDecl node: EnumCaseDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + enumCaseDecl node: EnumCaseDeclSyntax, + in typeContext: ImportedNominalType? + ) { guard let typeContext else { self.log.info("Enum case must be within a current type; \(node)") return @@ -182,7 +199,11 @@ final class Swift2JavaVisitor { } } - func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + variableDecl node: VariableDeclSyntax, + in typeContext: ImportedNominalType?, + sourceFilePath: String + ) { guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -232,7 +253,10 @@ final class Swift2JavaVisitor { } } - func visit(initializerDecl node: InitializerDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + initializerDecl node: InitializerDeclSyntax, + in typeContext: ImportedNominalType?, + ) { guard let typeContext else { self.log.info("Initializer must be within a current type; \(node)") return @@ -265,7 +289,10 @@ final class Swift2JavaVisitor { typeContext.initializers.append(imported) } - private func synthesizeRawRepresentableConformance(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { + private func synthesizeRawRepresentableConformance( + enumDecl node: EnumDeclSyntax, + in parent: ImportedNominalType? + ) { guard let imported = translator.importedNominalType(node, parent: parent) else { return } @@ -279,14 +306,15 @@ final class Swift2JavaVisitor { { if !imported.variables.contains(where: { $0.name == "rawValue" && $0.functionSignature.result.type != inheritanceType }) { let decl: DeclSyntax = "public var rawValue: \(raw: inheritanceType.description) { get }" - self.visit(decl: decl, in: imported) + self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } + // FIXME: why is this un-used imported.variables.first?.signatureString if !imported.initializers.contains(where: { $0.functionSignature.parameters.count == 1 && $0.functionSignature.parameters.first?.parameterName == "rawValue" && $0.functionSignature.parameters.first?.type == inheritanceType }) { let decl: DeclSyntax = "public init?(rawValue: \(raw: inheritanceType))" - self.visit(decl: decl, in: imported) + self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index a5b01bee..7e0f4885 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -195,7 +195,7 @@ extension SwiftFunctionSignature { guard parameterNode.specifier == nil else { throw SwiftFunctionTranslationError.genericParameterSpecifier(parameterNode) } - let param = try lookupContext.typeDeclaration(for: parameterNode) as! SwiftGenericParameterDeclaration + let param = try lookupContext.typeDeclaration(for: parameterNode, sourceFilePath: "FIXME_HAS_NO_PATH.swift") as! SwiftGenericParameterDeclaration params.append(param) if let inheritedNode = parameterNode.inheritedType { let inherited = try SwiftType(inheritedNode, lookupContext: lookupContext) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 45d5df5a..7acb1199 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -43,7 +43,7 @@ enum SwiftKnownModule: String { private var swiftSymbolTable: SwiftModuleSymbolTable { var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Swift", importedModules: [:]) - builder.handle(sourceFile: swiftSourceFile) + builder.handle(sourceFile: swiftSourceFile, sourceFilePath: "SwiftStdlib.swift") // FIXME: missing path here return builder.finalize() } @@ -53,7 +53,7 @@ private var foundationEssentialsSymbolTable: SwiftModuleSymbolTable { requiredAvailablityOfModuleWithName: "FoundationEssentials", alternativeModules: .init(isMainSourceOfSymbols: false, moduleNames: ["Foundation"]), importedModules: ["Swift": swiftSymbolTable]) - builder.handle(sourceFile: foundationEssentialsSourceFile) + builder.handle(sourceFile: foundationEssentialsSourceFile, sourceFilePath: "FakeFoundation.swift") return builder.finalize() } @@ -62,7 +62,7 @@ private var foundationSymbolTable: SwiftModuleSymbolTable { moduleName: "Foundation", alternativeModules: .init(isMainSourceOfSymbols: true, moduleNames: ["FoundationEssentials"]), importedModules: ["Swift": swiftSymbolTable]) - builder.handle(sourceFile: foundationSourceFile) + builder.handle(sourceFile: foundationSourceFile, sourceFilePath: "Foundation.swift") return builder.finalize() } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 763a5da2..0b8b5651 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -19,20 +19,37 @@ import SwiftSyntax public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax package class SwiftTypeDeclaration { + + // The short path from module root to the file in which this nominal was originally declared. + // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. + let sourceFilePath: String + /// 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. let moduleName: String - /// The name of this nominal type, e.g., 'MyCollection'. + /// The name of this nominal type, e.g. 'MyCollection'. let name: String - init(moduleName: String, name: String) { + init(sourceFilePath: String, moduleName: String, name: String) { + self.sourceFilePath = sourceFilePath self.moduleName = moduleName self.name = name } } +/// A syntax node paired with a simple file path +package struct SwiftJavaInputFile { + let syntax: SourceFileSyntax + /// Simple file path of the file from which the syntax node was parsed. + let path: String + package init(syntax: SourceFileSyntax, path: String) { + self.syntax = syntax + self.path = path + } +} + /// 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. package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { @@ -66,6 +83,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { /// Create a nominal type declaration from the syntax node for a nominal type /// declaration. init( + sourceFilePath: String, moduleName: String, parent: SwiftNominalTypeDeclaration?, node: NominalTypeDeclSyntaxNode @@ -82,7 +100,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } - super.init(moduleName: moduleName, name: node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } lazy var firstInheritanceType: TypeSyntax? = { @@ -145,11 +163,12 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { let syntax: GenericParameterSyntax init( + sourceFilePath: String, moduleName: String, node: GenericParameterSyntax ) { self.syntax = node - super.init(moduleName: moduleName, name: node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 8abb21f5..c5586410 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -50,7 +50,8 @@ struct SwiftParsedModuleSymbolTableBuilder { extension SwiftParsedModuleSymbolTableBuilder { mutating func handle( - sourceFile: SourceFileSyntax + sourceFile: SourceFileSyntax, + sourceFilePath: String ) { // Find top-level type declarations. for statement in sourceFile.statements { @@ -60,10 +61,10 @@ extension SwiftParsedModuleSymbolTableBuilder { } if let nominalTypeNode = decl.asNominal { - self.handle(nominalTypeDecl: nominalTypeNode, parent: nil) + self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalTypeNode, parent: nil) } if let extensionNode = decl.as(ExtensionDeclSyntax.self) { - self.handle(extensionDecl: extensionNode) + self.handle(extensionDecl: extensionNode, sourceFilePath: sourceFilePath) } } } @@ -71,6 +72,7 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add a nominal type declaration and all of the nested types within it to the symbol /// table. mutating func handle( + sourceFilePath: String, nominalTypeDecl node: NominalTypeDeclSyntaxNode, parent: SwiftNominalTypeDeclaration? ) { @@ -83,6 +85,7 @@ extension SwiftParsedModuleSymbolTableBuilder { // Otherwise, create the nominal type declaration. let nominalTypeDecl = SwiftNominalTypeDeclaration( + sourceFilePath: sourceFilePath, moduleName: moduleName, parent: parent, node: node @@ -96,26 +99,28 @@ extension SwiftParsedModuleSymbolTableBuilder { symbolTable.topLevelTypes[nominalTypeDecl.name] = nominalTypeDecl } - self.handle(memberBlock: node.memberBlock, parent: nominalTypeDecl) + self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: nominalTypeDecl) } mutating func handle( + sourceFilePath: String, memberBlock node: MemberBlockSyntax, parent: SwiftNominalTypeDeclaration ) { for member in node.members { // Find any nested types within this nominal type and add them. if let nominalMember = member.decl.asNominal { - self.handle(nominalTypeDecl: nominalMember, parent: parent) + self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalMember, parent: parent) } } } mutating func handle( - extensionDecl node: ExtensionDeclSyntax + extensionDecl node: ExtensionDeclSyntax, + sourceFilePath: String ) { - if !self.tryHandle(extension: node) { + if !self.tryHandle(extension: node, sourceFilePath: sourceFilePath) { self.unresolvedExtensions.append(node) } } @@ -123,7 +128,8 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add any nested types within the given extension to the symbol table. /// If the extended nominal type can't be resolved, returns false. mutating func tryHandle( - extension node: ExtensionDeclSyntax + extension node: ExtensionDeclSyntax, + sourceFilePath: String ) -> Bool { // Try to resolve the type referenced by this extension declaration. // If it fails, we'll try again later. @@ -141,7 +147,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } // Find any nested types within this extension and add them. - self.handle(memberBlock: node.memberBlock, parent: extendedNominal) + self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: extendedNominal) return true } @@ -158,7 +164,7 @@ extension SwiftParsedModuleSymbolTableBuilder { while !unresolvedExtensions.isEmpty { var extensions = self.unresolvedExtensions extensions.removeAll(where: { - self.tryHandle(extension: $0) + self.tryHandle(extension: $0, sourceFilePath: "FIXME_MISSING_FILEPATH.swift") // FIXME: missing filepath here in finalize }) // If we didn't resolve anything, we're done. diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 4271e297..ef299bab 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -39,7 +39,7 @@ extension SwiftSymbolTableProtocol { package class SwiftSymbolTable { let importedModules: [String: SwiftModuleSymbolTable] - let parsedModule:SwiftModuleSymbolTable + let parsedModule: SwiftModuleSymbolTable private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] private var prioritySortedImportedModules: [SwiftModuleSymbolTable] { @@ -55,15 +55,16 @@ package class SwiftSymbolTable { extension SwiftSymbolTable { package static func setup( moduleName: String, - _ sourceFiles: some Collection, + _ inputFiles: some Collection, log: Logger ) -> SwiftSymbolTable { // Prepare imported modules. // FIXME: Support arbitrary dependencies. var modules: Set = [] - for sourceFile in sourceFiles { - modules.formUnion(importingModules(sourceFile: sourceFile)) + for inputFile in inputFiles { + let importedModules = importingModules(sourceFile: inputFile.syntax) + modules.formUnion(importedModules) } var importedModules: [String: SwiftModuleSymbolTable] = [:] importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable @@ -84,8 +85,8 @@ extension SwiftSymbolTable { var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: moduleName, importedModules: importedModules, log: log) // First, register top-level and nested nominal types to the symbol table. - for sourceFile in sourceFiles { - builder.handle(sourceFile: sourceFile) + for sourceFile in inputFiles { + builder.handle(sourceFile: sourceFile.syntax, sourceFilePath: sourceFile.path) } let parsedModule = builder.finalize() return SwiftSymbolTable(parsedModule: parsedModule, importedModules: importedModules) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 9ede2b1b..f47669b5 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -48,7 +48,7 @@ class SwiftTypeLookupContext { } case .lookInMembers(let scopeNode): - if let nominalDecl = try typeDeclaration(for: scopeNode) { + if let nominalDecl = try typeDeclaration(for: scopeNode, sourceFilePath: "FIXME.swift") { // FIXME: no path here // implement some node -> file if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { return found } @@ -74,9 +74,9 @@ class SwiftTypeLookupContext { for name in names { switch name { case .identifier(let identifiableSyntax, _): - return try? typeDeclaration(for: identifiableSyntax) + return try? typeDeclaration(for: identifiableSyntax, sourceFilePath: "FIXME_NO_PATH.swift") // FIXME: how to get path here? case .declaration(let namedDeclSyntax): - return try? typeDeclaration(for: namedDeclSyntax) + return try? typeDeclaration(for: namedDeclSyntax, sourceFilePath: "FIXME_NO_PATH.swift") // FIXME: how to get path here? case .implicit(let implicitDecl): // TODO: Implement _ = implicitDecl @@ -90,7 +90,7 @@ class SwiftTypeLookupContext { /// Returns the type declaration object associated with the `Syntax` node. /// If there's no declaration created, create an instance on demand, and cache it. - func typeDeclaration(for node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + func typeDeclaration(for node: some SyntaxProtocol, sourceFilePath: String) throws -> SwiftTypeDeclaration? { if let found = typeDecls[node.id] { return found } @@ -98,17 +98,17 @@ class SwiftTypeLookupContext { let typeDecl: SwiftTypeDeclaration switch Syntax(node).as(SyntaxEnum.self) { case .genericParameter(let node): - typeDecl = SwiftGenericParameterDeclaration(moduleName: symbolTable.moduleName, node: node) + typeDecl = SwiftGenericParameterDeclaration(sourceFilePath: sourceFilePath, moduleName: symbolTable.moduleName, node: node) case .classDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .actorDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .structDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .enumDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .protocolDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .typeAliasDecl: fatalError("typealias not implemented") case .associatedTypeDecl: @@ -122,8 +122,9 @@ class SwiftTypeLookupContext { } /// Create a nominal type declaration instance for the specified syntax node. - private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode) throws -> SwiftNominalTypeDeclaration { + private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode, sourceFilePath: String) throws -> SwiftNominalTypeDeclaration { SwiftNominalTypeDeclaration( + sourceFilePath: sourceFilePath, moduleName: self.symbolTable.moduleName, parent: try parentTypeDecl(for: node), node: node @@ -136,7 +137,7 @@ class SwiftTypeLookupContext { while let parentDecl = node.ancestorDecl { switch parentDecl.as(DeclSyntaxEnum.self) { case .structDecl, .classDecl, .actorDecl, .enumDecl, .protocolDecl: - return (try typeDeclaration(for: parentDecl) as! SwiftNominalTypeDeclaration) + return (try typeDeclaration(for: parentDecl, sourceFilePath: "FIXME_NO_SOURCE_FILE.swift") as! SwiftNominalTypeDeclaration) // FIXME: need to get the source file of the parent default: node = parentDecl continue diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift index 7443039a..bb574c8a 100644 --- a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift @@ -190,8 +190,8 @@ extension JavaVirtualMachine { // If we failed to attach, report that. if let attachError = VMError(fromJNIError: attachResult) { + // throw attachError fatalError("JVM Error: \(attachError)") - throw attachError } JavaVirtualMachine.destroyTLS.set(jniEnv!) diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 66563fee..001d34fa 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -42,7 +42,7 @@ func assertOutput( let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) - try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) + try! translator.analyze(path: "/fake/Fake.swiftinterface", text: input) let output: String var printer: CodePrinter = CodePrinter(mode: .accumulateAll) diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 82747ec9..79b51c19 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -42,7 +42,7 @@ final class FuncCallbackImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! @@ -131,7 +131,7 @@ final class FuncCallbackImportTests { config.swiftModule = "__FakeModule" let st = Swift2JavaTranslator(config: config) - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }! @@ -247,7 +247,7 @@ final class FuncCallbackImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index ff19f4a2..b6ae6f6c 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -239,7 +239,7 @@ extension FunctionDescriptorTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel - try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == methodIdentifier @@ -273,7 +273,7 @@ extension FunctionDescriptorTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel - try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) let generator = FFMSwift2JavaGenerator( config: config, diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 938b5e7f..6ba93016 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -70,7 +70,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let generator = FFMSwift2JavaGenerator( config: config, @@ -110,7 +110,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalTakeInt" @@ -152,7 +152,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalTakeIntLongString" @@ -196,7 +196,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalReturnClass" @@ -240,7 +240,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "swapRawBufferPointer" @@ -287,7 +287,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.name == "helloMemberFunction" @@ -330,7 +330,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.name == "makeInt" @@ -373,7 +373,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.initializers.first { $0.name == "init" @@ -418,7 +418,7 @@ final class MethodImportTests { st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftStruct"]!.initializers.first { $0.name == "init" diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index fdbf2d5f..b437454c 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -34,7 +34,10 @@ struct SwiftSymbolTableSuite { """ let symbolTable = SwiftSymbolTable.setup( moduleName: "MyModule", - [sourceFile1, sourceFile2], + [ + .init(syntax: sourceFile1, path: "Fake.swift"), + .init(syntax: sourceFile2, path: "Fake2.swift") + ], log: Logger(label: "swift-java", logLevel: .critical) ) From 8560a5b0048ba09f4b2d974daafd9c8c2617fa5a Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 16:07:32 +0100 Subject: [PATCH 164/178] [JExtract/JNI] Add support for `async` Swift methods (#413) --- .github/workflows/pull_request.yml | 20 +- Package.swift | 31 +- README.md | 12 +- .../SwiftAndJavaJarSampleLib/Package.swift | 2 +- .../Package.swift | 2 +- .../Package.swift | 4 +- .../Sources/MySwiftLibrary/Async.swift | 38 ++ .../Sources/MySwiftLibrary/MySwiftError.swift | 17 + .../Sources/MySwiftLibrary/Optionals.swift | 4 + .../com/example/swift/AsyncBenchmark.java | 60 +++ .../java/com/example/swift/AsyncTest.java | 77 ++++ .../com/example/swift/MySwiftClassTest.java | 8 + .../java/com/example/swift/OptionalsTest.java | 13 +- .../Convenience/JavaType+Extensions.swift | 28 +- .../Convenience/SwiftSyntax+Extensions.swift | 4 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 5 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 97 ++++- ...wift2JavaGenerator+NativeTranslation.swift | 141 +++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 48 ++- Sources/JExtractSwiftLib/JavaParameter.swift | 1 + .../JavaTypes/JavaType+JDK.swift | 5 + .../SwiftTypes/SwiftEffectSpecifier.swift | 1 + .../SwiftTypes/SwiftFunctionSignature.swift | 14 +- .../SwiftTypes/SwiftType.swift | 2 +- .../SwiftTypes/SwiftTypeLookupContext.swift | 16 +- Sources/JavaTypes/JavaType+JNI.swift | 6 +- Sources/JavaTypes/JavaType+JavaSource.swift | 14 +- Sources/JavaTypes/JavaType+SwiftNames.swift | 6 +- Sources/JavaTypes/JavaType.swift | 2 +- Sources/JavaTypes/Mangling.swift | 2 +- .../Configuration.swift | 6 + .../JExtract/JExtractAsyncFuncMode.swift | 33 ++ .../JExtract/JExtractGenerationMode.swift | 22 ++ .../JExtractMemoryManagementMode.swift | 34 ++ .../JExtractMinimumAccessLevelMode.swift | 26 ++ .../JExtractUnsignedIntegerMode.swift} | 45 +-- .../Documentation.docc/SupportedFeatures.md | 21 +- .../DefaultCaches.swift | 67 ++++ .../_JNIBoxedConversions.swift | 105 ++++++ .../_JNIMethodIDCache.swift | 21 +- .../Commands/JExtractCommand.swift | 5 + .../SwiftRuntimeFunctions.swift} | 0 SwiftKitCore/build.gradle | 6 +- .../core/ConfinedSwiftMemorySession.java | 13 +- .../org/swift/swiftkit/core/SwiftArena.java | 2 +- .../swift/swiftkit/core/SwiftLibraries.java | 12 +- SwiftKitFFM/build.gradle | 6 +- .../swiftkit/ffm/AllocatingSwiftArena.java | 2 +- .../ffm/FFMConfinedSwiftMemorySession.java | 4 +- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 10 +- .../JNI/JNIAsyncTests.swift | 348 ++++++++++++++++++ .../JNI/JNIClassTests.swift | 20 +- .../JNI/JNIClosureTests.swift | 14 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 14 +- .../JNI/JNIJavaKitTests.swift | 2 +- .../JNI/JNIModuleTests.swift | 8 +- .../JNI/JNIOptionalTests.swift | 14 +- .../JNI/JNIProtocolTests.swift | 16 +- .../JNI/JNIStructTests.swift | 8 +- .../JNI/JNIVariablesTests.swift | 22 +- 64 files changed, 1338 insertions(+), 262 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift rename Sources/SwiftJavaConfigurationShared/{GenerationMode.swift => JExtract/JExtractUnsignedIntegerMode.swift} (68%) create mode 100644 Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift create mode 100644 Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift rename Sources/{SwiftJava/Helpers => SwiftJavaRuntimeSupport}/_JNIMethodIDCache.swift (67%) rename Sources/{SwiftKitSwift/SwiftKit.swift => SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift} (100%) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f2a1db71..5e5b767a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -26,11 +26,11 @@ jobs: name: Documentation check runs-on: ubuntu-latest container: - image: 'swift:6.1-noble' + image: 'swift:6.2-noble' strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] steps: @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -74,7 +74,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -98,7 +98,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -118,7 +118,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -140,7 +140,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -162,7 +162,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -182,7 +182,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -210,7 +210,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] # no nightly testing on macOS + swift_version: ['6.2'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names diff --git a/Package.swift b/Package.swift index f6cc83e7..09743135 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport @@ -169,9 +169,14 @@ let package = Package( // Support library written in Swift for SwiftKit "Java" .library( - name: "SwiftKitSwift", + name: "SwiftJavaRuntimeSupport", + targets: ["SwiftJavaRuntimeSupport"] + ), + + .library( + name: "SwiftRuntimeFunctions", type: .dynamic, - targets: ["SwiftKitSwift"] + targets: ["SwiftRuntimeFunctions"] ), .library( @@ -197,7 +202,7 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", from: "601.0.1"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .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"), @@ -213,7 +218,8 @@ let package = Package( name: "SwiftJavaDocumentation", dependencies: [ "SwiftJava", - "SwiftKitSwift", + "SwiftJavaRuntimeSupport", + "SwiftRuntimeFunctions", ] ), @@ -351,8 +357,19 @@ let package = Package( ] ), .target( - name: "SwiftKitSwift", - dependencies: [], + name: "SwiftJavaRuntimeSupport", + dependencies: [ + "CSwiftJavaJNI", + "SwiftJava" + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ] + ), + + .target( + name: "SwiftRuntimeFunctions", swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/README.md b/README.md index 2688f288..6f9a5fde 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ It is possible to generate Swift bindings to Java libraries using SwiftJava by u Required language/runtime versions: - **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integratio -- **Swift 6.0.x**, because the library uses modern Swift macros +- **Swift 6.2.x**, because the library uses modern Swift macros **swift-java jextract** @@ -73,7 +73,7 @@ This does require the use of the relatively recent [JEP-454: Foreign Function & This is the primary way we envision calling Swift code from server-side Java libraries and applications. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files +- **Swift 6.2**, because of dependence on rich swift interface files - **JDK 25+** - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-25. @@ -85,7 +85,7 @@ This mode is more limited in some performance and flexibility that it can offer, We recommend this mode when FFM is not available, or wide ranging deployment compatibility is your priority. When performance is paramaunt, we recommend the FFM mode instead. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files +- **Swift 6.2**, because of dependence on rich swift interface files - **Java 7+**, including @@ -94,7 +94,7 @@ Required language/runtime versions: This project contains multiple builds, living side by side together. You will need to have: -- Swift (6.1.x+) +- Swift (6.2.x+) - Java (25+ for FFM, even though we support lower JDK targets) - Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) @@ -104,7 +104,7 @@ Install **Swift**, the easiest way to do this is to use **Swiftly**: [swift.org/ This should automatically install a recent Swift, but you can always make sure by running: ```bash -swiftly install 6.1.2 --use +swiftly install 6.2 --use ``` Install a recent enough Java distribution. We validate this project using Corretto so you can choose to use that as well, @@ -229,4 +229,4 @@ xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc **This project is under active development. We welcome feedback about any issues you encounter.** -There is no guarantee about API stability until the project reaches a 1.0 release. \ No newline at end of file +There is no guarantee about API stability until the project reaches a 1.0 release. diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index c350a0e7..32ffbb28 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -63,7 +63,7 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift index b9765c24..98d1bd33 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -65,7 +65,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index c7d5d717..33a35c49 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport @@ -62,7 +62,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), ], exclude: [ "swift-java.config" diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift new file mode 100644 index 00000000..ebef1892 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +public func asyncSum(i1: Int64, i2: Int64) async -> Int64 { + return i1 + i2 +} + +public func asyncSleep() async throws { + try await Task.sleep(for: .milliseconds(500)) +} + +public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass { + let new = MySwiftClass(x: myClass.x, y: myClass.y) + try await Task.sleep(for: .milliseconds(500)) + return new +} + +public func asyncOptional(i: Int64) async throws -> Int64? { + try await Task.sleep(for: .milliseconds(100)) + return i +} + +public func asyncThrows() async throws { + throw MySwiftError.swiftError +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift new file mode 100644 index 00000000..d9d77d38 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.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 MySwiftError: Error { + case swiftError +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift index 3e122102..ca1d7458 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -62,6 +62,10 @@ public func optionalJavaKitLong(input: Optional) -> Int64? { } } +public func optionalThrowing() throws -> Int64? { + throw MySwiftError.swiftError +} + public func multipleOptionals( input1: Optional, input2: Optional, diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java new file mode 100644 index 00000000..9cc74246 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.swift.swiftkit.core.ClosableSwiftArena; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Fork(value = 1, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) +public class AsyncBenchmark { + /** + * Parameter for the number of parallel tasks to launch. + */ + @Param({"100", "500", "1000"}) + public int taskCount; + + @Setup(Level.Trial) + public void beforeAll() {} + + @TearDown(Level.Trial) + public void afterAll() {} + + @Benchmark + public void asyncSum(Blackhole bh) { + CompletableFuture[] futures = new CompletableFuture[taskCount]; + + // Launch all tasks in parallel using supplyAsync on our custom executor + for (int i = 0; i < taskCount; i++) { + futures[i] = MySwiftLibrary.asyncSum(10, 5).thenAccept(bh::consume); + } + + // Wait for all futures to complete. + CompletableFuture.allOf(futures).join(); + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java new file mode 100644 index 00000000..ae6e7cc7 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftClass; +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import java.time.Duration; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class AsyncTest { + @Test + void asyncSum() { + CompletableFuture future = MySwiftLibrary.asyncSum(10, 12); + + Long result = future.join(); + assertEquals(22, result); + } + + @Test + void asyncSleep() { + CompletableFuture future = MySwiftLibrary.asyncSleep(); + future.join(); + } + + @Test + void asyncCopy() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass obj = MySwiftClass.init(10, 5, arena); + CompletableFuture future = MySwiftLibrary.asyncCopy(obj, arena); + + MySwiftClass result = future.join(); + + assertEquals(10, result.getX()); + assertEquals(5, result.getY()); + } + } + + @Test + void asyncThrows() { + CompletableFuture future = MySwiftLibrary.asyncThrows(); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); + + Throwable cause = ex.getCause(); + assertNotNull(cause); + assertEquals(Exception.class, cause.getClass()); + assertEquals("swiftError", cause.getMessage()); + } + + @Test + void asyncOptional() { + CompletableFuture future = MySwiftLibrary.asyncOptional(42); + assertEquals(OptionalLong.of(42), future.join()); + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 2e9a7e62..fba8f13f 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -152,4 +152,12 @@ void addXWithJavaLong() { assertEquals(70, c1.addXWithJavaLong(javaLong)); } } + + @Test + void getAsyncVariable() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + assertEquals(42, c1.getGetAsync().join()); + } + } } \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java index d60ff6d5..d26de1d1 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -14,6 +14,7 @@ package com.example.swift; +import com.example.swift.MySwiftLibrary; import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.SwiftArena; @@ -22,8 +23,7 @@ import java.util.OptionalInt; import java.util.OptionalLong; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class OptionalsTest { @Test @@ -113,4 +113,13 @@ void multipleOptionals() { assertEquals(result, OptionalLong.of(1L)); } } + + @Test + void optionalThrows() { + try (var arena = SwiftArena.ofConfined()) { + Exception exception = assertThrows(Exception.class, () -> MySwiftLibrary.optionalThrowing()); + + assertEquals("swiftError", exception.getMessage()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index cb849e79..3b29fcd3 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -25,7 +25,7 @@ extension JavaType { case .long: return "J" case .float: return "F" case .double: return "D" - case .class(let package, let name): + case .class(let package, let name, _): let nameWithInnerClasses = name.replacingOccurrences(of: ".", with: "$") if let package { return "L\(package.replacingOccurrences(of: ".", with: "/"))/\(nameWithInnerClasses);" @@ -111,4 +111,30 @@ extension JavaType { false } } + + /// Returns the boxed type, or self if the type is already a Java class. + var boxedType: JavaType { + switch self { + case .boolean: + return .class(package: "java.lang", name: "Boolean") + case .byte: + return .class(package: "java.lang", name: "Byte") + case .char: + return .class(package: "java.lang", name: "Character") + case .short: + return .class(package: "java.lang", name: "Short") + case .int: + return .class(package: "java.lang", name: "Integer") + case .long: + return .class(package: "java.lang", name: "Long") + case .float: + return .class(package: "java.lang", name: "Float") + case .double: + return .class(package: "java.lang", name: "Double") + case .void: + return .class(package: "java.lang", name: "Void") + default: + return self + } + } } diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index 8a58f42a..d3902aa4 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftDiagnostics -import SwiftSyntax +@_spi(ExperimentalLanguageFeatures) import SwiftSyntax extension WithModifiersSyntax { var accessControlModifiers: DeclModifierListSyntax { @@ -218,6 +218,8 @@ extension DeclSyntaxProtocol { } else { "var" } + case .usingDecl(let node): + node.nameForDebug } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 188f5e8b..a4485fff 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -453,7 +453,7 @@ extension FFMSwift2JavaGenerator { func renderMemoryLayoutValue(for javaType: JavaType) -> String { if let layout = ForeignValueLayout(javaType: javaType) { return layout.description - } else if case .class(package: _, name: let customClass) = javaType { + } else if case .class(package: _, name: let customClass, _) = javaType { return ForeignValueLayout(customType: customClass).description } else { fatalError("renderMemoryLayoutValue not supported for \(javaType)") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 5ef266c8..f233ef00 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -108,7 +108,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftRuntimeFunctions """) @@ -136,7 +136,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftRuntimeFunctions """ ) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index f9ba88b9..c445d5c9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -189,7 +189,7 @@ extension FFMSwift2JavaGenerator { private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); return true; } @@ -339,7 +339,7 @@ extension FFMSwift2JavaGenerator { private static SymbolLookup getSymbolLookup() { if (SwiftLibraries.AUTO_LOAD_LIBS) { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 1f0d3efc..84cc43c0 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -192,6 +192,10 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { self.functionSignature.effectSpecifiers.contains(.throws) } + var isAsync: Bool { + self.functionSignature.isAsync + } + init( module: String, swiftDecl: any DeclSyntaxProtocol, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 258b537e..ce6ec18e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -429,7 +429,7 @@ extension JNISwift2JavaGenerator { let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } - let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" + let throwsClause = translatedDecl.isThrowing && !translatedDecl.isAsync ? " throws Exception" : "" let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in guard case .generic(let name, let extends) = parameter.parameter.type else { @@ -483,7 +483,6 @@ extension JNISwift2JavaGenerator { printNativeFunction(&printer, translatedDecl) } - } private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { @@ -523,7 +522,7 @@ extension JNISwift2JavaGenerator { // Indirect return receivers for outParameter in translatedFunctionSignature.resultType.outParameters { - printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());") + printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render(type: outParameter.type));") arguments.append(outParameter.name) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a0178d3c..56174e3e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -118,6 +118,7 @@ extension JNISwift2JavaGenerator { name: getAsCaseName, isStatic: false, isThrowing: false, + isAsync: false, nativeFunctionName: "$\(getAsCaseName)", parentName: enumName, functionTypes: [], @@ -181,13 +182,13 @@ extension JNISwift2JavaGenerator { } // Swift -> Java - let translatedFunctionSignature = try translate( + var translatedFunctionSignature = try translate( functionSignature: decl.functionSignature, methodName: javaName, parentName: parentName ) // Java -> Java (native) - let nativeFunctionSignature = try nativeTranslation.translate( + var nativeFunctionSignature = try nativeTranslation.translate( functionSignature: decl.functionSignature, translatedFunctionSignature: translatedFunctionSignature, methodName: javaName, @@ -212,10 +213,21 @@ extension JNISwift2JavaGenerator { } } + // Handle async methods + if decl.functionSignature.isAsync { + self.convertToAsync( + translatedFunctionSignature: &translatedFunctionSignature, + nativeFunctionSignature: &nativeFunctionSignature, + originalFunctionSignature: decl.functionSignature, + mode: config.effectiveAsyncFuncMode + ) + } + return TranslatedFunctionDecl( name: javaName, isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, isThrowing: decl.isThrowing, + isAsync: decl.isAsync, nativeFunctionName: "$\(javaName)", parentName: parentName, functionTypes: funcTypes, @@ -470,6 +482,50 @@ extension JNISwift2JavaGenerator { } } + func convertToAsync( + translatedFunctionSignature: inout TranslatedFunctionSignature, + nativeFunctionSignature: inout NativeFunctionSignature, + originalFunctionSignature: SwiftFunctionSignature, + mode: JExtractAsyncFuncMode + ) { + switch mode { + case .completableFuture: + // Update translated function + + let nativeFutureType = JavaType.completableFuture(nativeFunctionSignature.result.javaType) + + let futureOutParameter = OutParameter( + name: "$future", + type: nativeFutureType, + allocation: .new + ) + + let result = translatedFunctionSignature.resultType + translatedFunctionSignature.resultType = TranslatedResult( + javaType: .completableFuture(translatedFunctionSignature.resultType.javaType), + annotations: result.annotations, + outParameters: result.outParameters + [futureOutParameter], + conversion: .aggregate(variable: nil, [ + .print(.placeholder), // Make the downcall + .method(.constant("$future"), function: "thenApply", arguments: [ + .lambda(args: ["futureResult$"], body: .replacingPlaceholder(result.conversion, placeholder: "futureResult$")) + ]) + ]) + ) + + // Update native function + nativeFunctionSignature.result.javaType = .void + nativeFunctionSignature.result.conversion = .asyncCompleteFuture( + nativeFunctionSignature.result.conversion, + swiftFunctionResultType: originalFunctionSignature.result.type, + nativeReturnType: nativeFunctionSignature.result.javaType, + outParameters: nativeFunctionSignature.result.outParameters, + isThrowing: originalFunctionSignature.isThrowing + ) + nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) + } + } + func translateProtocolParameter( protocolType: SwiftType, parameterName: String, @@ -753,6 +809,8 @@ extension JNISwift2JavaGenerator { let isThrowing: Bool + let isAsync: Bool + /// The name of the native function let nativeFunctionName: String @@ -812,11 +870,15 @@ extension JNISwift2JavaGenerator { struct OutParameter { enum Allocation { case newArray(JavaType, size: Int) + case new - func render() -> String { + func render(type: JavaType) -> String { switch self { case .newArray(let javaType, let size): "new \(javaType)[\(size)]" + + case .new: + "new \(type)()" } } } @@ -912,6 +974,12 @@ extension JNISwift2JavaGenerator { /// Access a member of the value indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String) + /// `(args) -> { return body; }` + indirect case lambda(args: [String] = [], body: JavaNativeConversionStep) + + /// Prints the conversion step, ignoring the output. + indirect case print(JavaNativeConversionStep) + /// 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. @@ -1024,6 +1092,23 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, let placeholder): return inner.render(&printer, placeholder) + + case .lambda(let args, let body): + var printer = CodePrinter() + printer.printBraceBlock("(\(args.joined(separator: ", "))) ->") { printer in + let body = body.render(&printer, placeholder) + if !body.isEmpty { + printer.print("return \(body);") + } else { + printer.print("return;") + } + } + return printer.finalize() + + case .print(let inner): + let inner = inner.render(&printer, placeholder) + printer.print("\(inner);") + return "" } } @@ -1074,6 +1159,12 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, _): return inner.requiresSwiftArena + + case .lambda(_, let body): + return body.requiresSwiftArena + + case .print(let inner): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index bdbfe2f1..45208d7b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -59,34 +59,15 @@ extension JNISwift2JavaGenerator { nil } - return try NativeFunctionSignature( + let result = try translate(swiftResult: functionSignature.result) + + return NativeFunctionSignature( selfParameter: nativeSelf, parameters: parameters, - result: translate(swiftResult: functionSignature.result) + result: result ) } - func translateParameters( - _ parameters: [SwiftParameter], - translatedParameters: [TranslatedParameter], - methodName: String, - parentName: String, - genericParameters: [SwiftGenericParameterDeclaration], - genericRequirements: [SwiftGenericRequirement] - ) throws -> [NativeParameter] { - try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in - let parameterName = translatedParameter.parameter.name - return try translateParameter( - type: swiftParameter.type, - parameterName: parameterName, - methodName: methodName, - parentName: parentName, - genericParameters: genericParameters, - genericRequirements: genericRequirements - ) - } - } - func translateParameter( type: SwiftType, parameterName: String, @@ -508,8 +489,8 @@ extension JNISwift2JavaGenerator { struct NativeFunctionSignature { let selfParameter: NativeParameter? - let parameters: [NativeParameter] - let result: NativeResult + var parameters: [NativeParameter] + var result: NativeResult } struct NativeParameter { @@ -522,8 +503,8 @@ extension JNISwift2JavaGenerator { } struct NativeResult { - let javaType: JavaType - let conversion: NativeSwiftConversionStep + var javaType: JavaType + var conversion: NativeSwiftConversionStep /// Out parameters for populating the indirect return values. var outParameters: [JavaParameter] @@ -545,7 +526,7 @@ extension JNISwift2JavaGenerator { /// `value.getJValue(in:)` indirect case getJValue(NativeSwiftConversionStep) - /// `SwiftType(from: value, in: environment!)` + /// `SwiftType(from: value, in: environment)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) indirect case extractSwiftProtocolValue( @@ -588,6 +569,14 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) + indirect case asyncCompleteFuture( + NativeSwiftConversionStep, + swiftFunctionResultType: SwiftType, + nativeReturnType: JavaType, + outParameters: [JavaParameter], + isThrowing: Bool + ) + /// 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. @@ -604,15 +593,15 @@ extension JNISwift2JavaGenerator { case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJNIValue(in: environment!)" + return "\(inner).getJNIValue(in: environment)" case .getJValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJValue(in: environment!)" + return "\(inner).getJValue(in: environment)" case .initFromJNI(let inner, let swiftType): let inner = inner.render(&printer, placeholder) - return "\(swiftType)(fromJNI: \(inner), in: environment!)" + return "\(swiftType)(fromJNI: \(inner), in: environment)" case .extractSwiftProtocolValue(let inner, let typeMetadataVariableName, let protocolNames): let inner = inner.render(&printer, placeholder) @@ -624,11 +613,11 @@ extension JNISwift2JavaGenerator { // TODO: Remove the _openExistential when we decide to only support language mode v6+ printer.print( """ - guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment!))) else { + guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment))) else { fatalError("\(typeMetadataVariableName) memory address was null") } let \(inner)DynamicType$: Any.Type = unsafeBitCast(\(inner)TypeMetadataPointer$, to: Any.Type.self) - guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment!))) else { + guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment))) else { fatalError("\(inner) memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -651,7 +640,7 @@ extension JNISwift2JavaGenerator { } printer.print( """ - let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) + let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) """ ) @@ -709,13 +698,13 @@ extension JNISwift2JavaGenerator { printer.print( """ - let class$ = environment!.interface.GetObjectClass(environment, \(placeholder)) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! + let class$ = environment.interface.GetObjectClass(environment, \(placeholder)) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! let arguments$: [jvalue] = [\(arguments.joined(separator: ", "))] """ ) - let upcall = "environment!.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let upcall = "environment.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" let result = nativeResult.conversion.render(&printer, upcall) if nativeResult.javaType.isVoid { @@ -731,7 +720,7 @@ extension JNISwift2JavaGenerator { case .initializeJavaKitWrapper(let inner, let wrapperName): let inner = inner.render(&printer, placeholder) - return "\(wrapperName)(javaThis: \(inner), environment: environment!)" + return "\(wrapperName)(javaThis: \(inner), environment: environment)" case .optionalLowering(let valueConversion, let discriminatorName, let valueName): let value = valueConversion.render(&printer, valueName) @@ -815,6 +804,82 @@ extension JNISwift2JavaGenerator { """ ) return unwrappedName + + case .asyncCompleteFuture( + let inner, + let swiftFunctionResultType, + let nativeReturnType, + let outParameters, + let isThrowing + ): + // Global ref all indirect returns + for outParameter in outParameters { + printer.print("let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))") + } + + printer.print( + """ + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + """ + ) + + func printDo(printer: inout CodePrinter) { + printer.print("let swiftResult$ = await \(placeholder)") + printer.print("environment = try JavaVirtualMachine.shared().environment()") + let inner = inner.render(&printer, "swiftResult$") + if swiftFunctionResultType.isVoid { + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") + } else { + printer.printBraceBlock("withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)])") { printer in + printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0)") + } + } + } + + func printTask(printer: inout CodePrinter) { + printer.printBraceBlock("defer") { printer in + // Defer might on any thread, so we need to attach environment. + printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)") + for outParameter in outParameters { + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(outParameter.name))") + } + } + if isThrowing { + printer.printBraceBlock("do") { printer in + printDo(printer: &printer) + } + printer.printBraceBlock("catch") { printer in + // We might not be on the same thread after the suspension, so we need to attach the thread again. + printer.print( + """ + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + """ + ) + } + } else { + printDo(printer: &printer) + } + } + + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("Task.immediate") { printer in + // Immediate runs on the caller thread, so we don't need to attach the environment again. + printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. + printTask(printer: &printer) + } + } + printer.printBraceBlock("else") { printer in + printer.printBraceBlock("Task") { printer in + // We can be on any thread, so we need to attach the thread. + printer.print("var environment = try! JavaVirtualMachine.shared().environment()") + printTask(printer: &printer) + } + } + + return "" } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 1d4f81dc..5403ae40 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -353,29 +353,42 @@ extension JNISwift2JavaGenerator { } // Lower the result. - let innerBody: String - if !decl.functionSignature.result.type.isVoid { + func innerBody(in printer: inout CodePrinter) -> String { let loweredResult = nativeSignature.result.conversion.render(&printer, result) - innerBody = "return \(loweredResult)" - } else { - innerBody = result + + if !decl.functionSignature.result.type.isVoid { + return "return \(loweredResult)" + } else { + return loweredResult + } } - if decl.isThrowing { - // TODO: Handle classes for dummy value - let dummyReturn = !nativeSignature.result.javaType.isVoid ? "return \(decl.functionSignature.result.type).jniPlaceholderValue" : "" + if decl.isThrowing, !decl.isAsync { + let dummyReturn: String + + if nativeSignature.result.javaType.isVoid { + dummyReturn = "" + } else { + // We assume it is something that implements JavaValue + dummyReturn = "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue" + } + + printer.print("do {") + printer.indent() + printer.print(innerBody(in: &printer)) + printer.outdent() + printer.print("} catch {") + printer.indent() printer.print( - """ - do { - \(innerBody) - } catch { - environment.throwAsException(error) - \(dummyReturn) - } - """ + """ + environment.throwAsException(error) + \(dummyReturn) + """ ) + printer.outdent() + printer.print("}") } else { - printer.print(innerBody) + printer.print(innerBody(in: &printer)) } } @@ -453,6 +466,7 @@ extension JNISwift2JavaGenerator { import SwiftJava import CSwiftJavaJNI + import SwiftJavaRuntimeSupport """ ) diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 43f5a2b4..2670e6ee 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -24,6 +24,7 @@ struct JavaParameter { switch self { case .concrete(let type): return type.jniTypeSignature + case .generic(_, let extends): guard !extends.isEmpty else { return "Ljava/lang/Object;" diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 02850801..5e5a7268 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -39,4 +39,9 @@ extension JavaType { static var javaLangThrowable: JavaType { .class(package: "java.lang", name: "Throwable") } + + /// The description of the type java.util.concurrent.CompletableFuture + static func completableFuture(_ T: JavaType) -> JavaType { + .class(package: "java.util.concurrent", name: "CompletableFuture", typeParameters: [T.boxedType]) + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift index 9ba4d7ad..fb28586b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift @@ -14,4 +14,5 @@ enum SwiftEffectSpecifier: Equatable { case `throws` + case `async` } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 7e0f4885..1f2fbd36 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -30,6 +30,14 @@ public struct SwiftFunctionSignature: Equatable { var genericParameters: [SwiftGenericParameterDeclaration] var genericRequirements: [SwiftGenericRequirement] + var isAsync: Bool { + effectSpecifiers.contains(.async) + } + + var isThrowing: Bool { + effectSpecifiers.contains(.throws) + } + init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], @@ -245,8 +253,8 @@ extension SwiftFunctionSignature { if signature.effectSpecifiers?.throwsClause != nil { effectSpecifiers.append(.throws) } - if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + if signature.effectSpecifiers?.asyncSpecifier != nil { + effectSpecifiers.append(.async) } let parameters = try signature.parameterClause.parameters.map { param in @@ -331,7 +339,7 @@ extension SwiftFunctionSignature { effectSpecifiers.append(.throws) } if let asyncSpecifier = decl.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + effectSpecifiers.append(.async) } return effectSpecifiers } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 81afe637..3840b3e1 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -215,7 +215,7 @@ extension SwiftType { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, - .packElementType, .packExpansionType, .suppressedType: + .packElementType, .packExpansionType, .suppressedType, .inlineArrayType: throw TypeTranslationError.unimplementedType(type) case .attributedType(let attributedType): diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index f47669b5..13f42cfc 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -42,24 +42,19 @@ class SwiftTypeLookupContext { return typeDeclaration(for: names) } - case .fromFileScope(_, let names): - if !names.isEmpty { - return typeDeclaration(for: names) - } - - case .lookInMembers(let scopeNode): + case .lookForMembers(let scopeNode): if let nominalDecl = try typeDeclaration(for: scopeNode, sourceFilePath: "FIXME.swift") { // FIXME: no path here // implement some node -> file if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { return found } } - case .lookInGenericParametersOfExtendedType(let extensionNode): + case .lookForGenericParameters(let extensionNode): // TODO: Implement _ = extensionNode break - case .mightIntroduceDollarIdentifiers: + case .lookForImplicitClosureParameters: // Dollar identifier can't be a type, ignore. break } @@ -81,8 +76,9 @@ class SwiftTypeLookupContext { // TODO: Implement _ = implicitDecl break - case .dollarIdentifier: - break + case .equivalentNames(let equivalentNames): + // TODO: Implement + _ = equivalentNames } } return nil diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 08361bac..b9b00ccb 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -34,9 +34,9 @@ extension JavaType { case .array(.float): "jfloatArray?" case .array(.double): "jdoubleArray?" case .array: "jobjectArray?" - case .class(package: "java.lang", name: "String"): "jstring?" - case .class(package: "java.lang", name: "Class"): "jclass?" - case .class(package: "java.lang", name: "Throwable"): "jthrowable?" + case .class(package: "java.lang", name: "String", _): "jstring?" + case .class(package: "java.lang", name: "Class", _): "jclass?" + case .class(package: "java.lang", name: "Throwable", _): "jthrowable?" case .class: "jobject?" } } diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index f9e2e0cd..f81f0c6d 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -51,9 +51,13 @@ extension JavaType: CustomStringConvertible { case .double: "double" case .void: "void" case .array(let elementType): "\(elementType.description)[]" - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, let typeParameters): if let package { - "\(package).\(name)" + if !typeParameters.isEmpty { + "\(package).\(name)<\(typeParameters.map(\.description).joined(separator: ", "))>" + } else { + "\(package).\(name)" + } } else { name } @@ -64,7 +68,7 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var className: String? { switch self { - case .class(_, let name): + case .class(_, let name, _): return name default: return nil @@ -75,9 +79,9 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var fullyQualifiedClassName: String? { switch self { - case .class(.some(let package), let name): + case .class(.some(let package), let name, _): return "\(package).\(name)" - case .class(nil, let name): + case .class(nil, let name, _): return name default: return nil diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 20de73fc..7015ef91 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -25,7 +25,7 @@ extension JavaType { .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return !stringIsValueType case .class: @@ -38,7 +38,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "Runnable"): + case .class(package: "java.lang", name: "Runnable", _): return true case .class: return false @@ -57,7 +57,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return true case .class: return false diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 2a2d901f..ce7191ba 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -28,7 +28,7 @@ public enum JavaType: Equatable, Hashable { /// A Java class separated into its package (e.g., "java.lang") and class name /// (e.g., "Object") - case `class`(package: String?, name: String) + case `class`(package: String?, name: String, typeParameters: [JavaType] = []) /// A Java array. indirect case array(JavaType) diff --git a/Sources/JavaTypes/Mangling.swift b/Sources/JavaTypes/Mangling.swift index f0dbd484..1311b717 100644 --- a/Sources/JavaTypes/Mangling.swift +++ b/Sources/JavaTypes/Mangling.swift @@ -35,7 +35,7 @@ extension JavaType { case .short: "S" case .void: "V" case .array(let elementType): "[" + elementType.mangledName - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, _): "L\(package!).\(name.replacingPeriodsWithDollars());".replacingPeriodsWithSlashes() } } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 2d9b4311..6d8d20e5 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -46,6 +46,7 @@ public struct Configuration: Codable { public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { unsignedNumbersMode ?? .default } + public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode { minimumInputAccessLevelMode ?? .default @@ -56,6 +57,11 @@ public struct Configuration: Codable { memoryManagementMode ?? .default } + public var asyncFuncMode: JExtractAsyncFuncMode? + public var effectiveAsyncFuncMode: JExtractAsyncFuncMode { + asyncFuncMode ?? .default + } + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift new file mode 100644 index 00000000..221649c5 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.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 +// +//===----------------------------------------------------------------------===// + +/// Configures how Swift `async` functions should be extracted by jextract. +public enum JExtractAsyncFuncMode: String, Codable { + /// Extract Swift `async` APIs as Java functions that return `CompletableFuture`s. + case completableFuture + + /// Extract Swift `async` APIs as Java functions that return `Future`s. + /// + /// This mode is useful for platforms that do not have `CompletableFuture` support, such as + /// Android 23 and below. + /// + /// - Note: Prefer using the `completableFuture` mode instead, if possible. +// case future +} + +extension JExtractAsyncFuncMode { + public static var `default`: Self { + .completableFuture + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift new file mode 100644 index 00000000..8df00cf0 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.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 +// +//===----------------------------------------------------------------------===// + +/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. +public enum JExtractGenerationMode: String, Codable { + /// Foreign Value and Memory API + case ffm + + /// Java Native Interface + case jni +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift new file mode 100644 index 00000000..be77d27d --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Configures how memory should be managed by the user +public enum JExtractMemoryManagementMode: String, Codable { + /// Force users to provide an explicit `SwiftArena` to all calls that require them. + case explicit + + /// Provide both explicit `SwiftArena` support + /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. + case allowGlobalAutomatic + + public static var `default`: Self { + .explicit + } + + public var requiresGlobalArena: Bool { + switch self { + case .explicit: false + case .allowGlobalAutomatic: true + } + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift new file mode 100644 index 00000000..22fead57 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// The minimum access level which +public enum JExtractMinimumAccessLevelMode: String, Codable { + case `public` + case `package` + case `internal` +} + +extension JExtractMinimumAccessLevelMode { + public static var `default`: Self { + .public + } +} diff --git a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift similarity index 68% rename from Sources/SwiftJavaConfigurationShared/GenerationMode.swift rename to Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift index 22fdd5f5..d7a94070 100644 --- a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift @@ -12,15 +12,6 @@ // //===----------------------------------------------------------------------===// -/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. -public enum JExtractGenerationMode: String, Codable { - /// Foreign Value and Memory API - case ffm - - /// Java Native Interface - case jni -} - /// Configures how Swift unsigned integers should be extracted by jextract. public enum JExtractUnsignedIntegerMode: String, Codable { /// Treat unsigned Swift integers as their signed equivalents in Java signatures, @@ -51,6 +42,7 @@ public enum JExtractUnsignedIntegerMode: String, Codable { // case widenOrAnnotate } + extension JExtractUnsignedIntegerMode { public var needsConversion: Bool { switch self { @@ -63,38 +55,3 @@ extension JExtractUnsignedIntegerMode { .annotate } } - -/// The minimum access level which -public enum JExtractMinimumAccessLevelMode: String, Codable { - case `public` - case `package` - case `internal` -} - -extension JExtractMinimumAccessLevelMode { - public static var `default`: Self { - .public - } -} - - -/// Configures how memory should be managed by the user -public enum JExtractMemoryManagementMode: String, Codable { - /// Force users to provide an explicit `SwiftArena` to all calls that require them. - case explicit - - /// Provide both explicit `SwiftArena` support - /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. - case allowGlobalAutomatic - - public static var `default`: Self { - .explicit - } - - public var requiresGlobalArena: Bool { - switch self { - case .explicit: false - case .allowGlobalAutomatic: true - } - } -} diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 997e680e..e97ed03e 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -57,7 +57,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Typed throws: `func x() throws(E)` | ❌ | ❌ | | Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | | Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | -| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | +| Async functions `func async` and properties: `var { get async {} }` | ❌ | ✅ | | Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | @@ -97,7 +97,6 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | | Value semantic types (e.g. struct copying) | ❌ | ❌ | -| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | | | | | | | | | @@ -328,3 +327,21 @@ which conform a to a given Swift protocol. #### Returning protocol types Protocols are not yet supported as return types. + +### `async` functions + +> Note: Importing `async` functions is currently only available in the JNI mode of jextract. + +Asynchronous functions in Swift can be extraced using different modes, which are explained below. + +#### Async function mode: completable-future (default) + +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.CompletableFuture`. +This mode gives the most flexibility and should be prefered if your platform supports `CompletableFuture`. + +#### Async mode: future + +This is a mode for legacy platforms, where `CompletableFuture` is not available, such as Android 23 and below. +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.Future`. +To enable this mode pass the `--async-func-mode future` command line option, +or set the `asyncFuncMode` configuration value in `swift-java.config` diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift new file mode 100644 index 00000000..1c3079bc --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +extension _JNIMethodIDCache { + public enum CompletableFuture { + private static let completeMethod = Method( + name: "complete", + signature: "(Ljava/lang/Object;)Z" + ) + + private static let completeExceptionallyMethod = Method( + name: "completeExceptionally", + signature: "(Ljava/lang/Throwable;)Z" + ) + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/util/concurrent/CompletableFuture", + methods: [completeMethod, completeExceptionallyMethod] + ) + + public static var `class`: jclass { + cache.javaClass + } + + /// CompletableFuture.complete(T) + public static var complete: jmethodID { + cache.methods[completeMethod]! + } + + /// CompletableFuture.completeExceptionally(Throwable) + public static var completeExceptionally: jmethodID { + cache.methods[completeExceptionallyMethod]! + } + } + + public enum Exception { + private static let messageConstructor = Method(name: "", signature: "(Ljava/lang/String;)V") + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Exception", + methods: [messageConstructor] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var constructWithMessage: jmethodID { + cache.methods[messageConstructor]! + } + } +} diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift new file mode 100644 index 00000000..68d98ffc --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// 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 CSwiftJavaJNI +import SwiftJava + +public enum _JNIBoxedConversions { + private static let booleanMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(Z)Ljava/lang/Boolean;", isStatic: true) + private static let byteMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(B)Ljava/lang/Byte;", isStatic: true) + private static let charMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(C)Ljava/lang/Character;", isStatic: true) + private static let shortMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(S)Ljava/lang/Short;", isStatic: true) + private static let intMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(I)Ljava/lang/Integer;", isStatic: true) + private static let longMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(J)Ljava/lang/Long;", isStatic: true) + private static let floatMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(F)Ljava/lang/Float;", isStatic: true) + private static let doubleMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(D)Ljava/lang/Double;", isStatic: true) + + private static let booleanCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Boolean", + methods: [booleanMethod] + ) + + private static let byteCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Byte", + methods: [byteMethod] + ) + private static let charCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Character", + methods: [charMethod] + ) + + private static let shortCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Short", + methods: [shortMethod] + ) + private static let intCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Integer", + methods: [intMethod] + ) + + private static let longCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Long", + methods: [longMethod] + ) + + private static let floatCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Float", + methods: [floatMethod] + ) + + private static let doubleCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Double", + methods: [doubleMethod] + ) + + public static func box(_ value: jboolean, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, booleanCache.javaClass, booleanCache.methods[booleanMethod]!, [jvalue(z: value)])! + } + + public static func box(_ value: jbyte, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, byteCache.javaClass, byteCache.methods[byteMethod]!, [jvalue(b: value)])! + } + + public static func box(_ value: jchar, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, charCache.javaClass, charCache.methods[charMethod]!, [jvalue(c: value)])! + } + + public static func box(_ value: jshort, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, shortCache.javaClass, shortCache.methods[shortMethod]!, [jvalue(s: value)])! + } + + public static func box(_ value: jint, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, intCache.javaClass, intCache.methods[intMethod]!, [jvalue(i: value)])! + } + + public static func box(_ value: jlong, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, longCache.javaClass, longCache.methods[longMethod]!, [jvalue(j: value)])! + } + + public static func box(_ value: jfloat, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, floatCache.javaClass, floatCache.methods[floatMethod]!, [jvalue(f: value)])! + } + + public static func box(_ value: jdouble, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, doubleCache.javaClass, doubleCache.methods[doubleMethod]!, [jvalue(d: value)])! + } +} diff --git a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift similarity index 67% rename from Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift rename to Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index a67d225f..dd7eb5d1 100644 --- a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -12,6 +12,9 @@ // //===----------------------------------------------------------------------===// +import CSwiftJavaJNI +import SwiftJava + /// A cache used to hold references for JNI method and classes. /// /// This type is used internally in by the outputted JExtract wrappers @@ -20,10 +23,12 @@ public final class _JNIMethodIDCache: Sendable { public struct Method: Hashable { public let name: String public let signature: String + public let isStatic: Bool - public init(name: String, signature: String) { + public init(name: String, signature: String, isStatic: Bool = false) { self.name = name self.signature = signature + self.isStatic = isStatic } } @@ -40,10 +45,18 @@ public final class _JNIMethodIDCache: Sendable { } self._class = environment.interface.NewGlobalRef(environment, clazz)! self.methods = methods.reduce(into: [:]) { (result, method) in - if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { - result[method] = methodID + if method.isStatic { + if let methodID = environment.interface.GetStaticMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Static method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } else { - fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index c730cbf6..aba79983 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -78,6 +78,9 @@ extension SwiftJava { """ ) var dependsOn: [String] = [] + + @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") + var asyncFuncMode: JExtractAsyncFuncMode = .default } } @@ -98,6 +101,7 @@ extension SwiftJava.JExtractCommand { config.unsignedNumbersMode = unsignedNumbers config.minimumInputAccessLevelMode = minimumInputAccessLevel config.memoryManagementMode = memoryManagementMode + config.asyncFuncMode = asyncFuncMode try checkModeCompatibility() @@ -162,3 +166,4 @@ extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} extension JExtractMemoryManagementMode: ExpressibleByArgument {} +extension JExtractAsyncFuncMode: ExpressibleByArgument {} diff --git a/Sources/SwiftKitSwift/SwiftKit.swift b/Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift similarity index 100% rename from Sources/SwiftKitSwift/SwiftKit.swift rename to Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 9e4891dc..f4376e94 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -73,10 +73,10 @@ tasks.test { } } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -84,7 +84,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + args("build", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 4383a6fe..3514d9c1 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -16,6 +16,8 @@ import java.util.LinkedList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; public class ConfinedSwiftMemorySession implements ClosableSwiftArena { @@ -23,21 +25,17 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final static int CLOSED = 0; final static int ACTIVE = 1; - final Thread owner; final AtomicInteger state; final ConfinedResourceList resources; - public ConfinedSwiftMemorySession(Thread owner) { - this.owner = owner; + public ConfinedSwiftMemorySession() { this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); } void checkValid() throws RuntimeException { - if (this.owner != null && this.owner != Thread.currentThread()) { - throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); - } else if (this.state.get() < ACTIVE) { + if (this.state.get() < ACTIVE) { throw new RuntimeException("SwiftArena is already closed!"); } } @@ -61,8 +59,7 @@ public void register(SwiftInstance instance) { } static final class ConfinedResourceList implements SwiftResourceList { - // TODO: Could use intrusive linked list to avoid one indirection here - final List resourceCleanups = new LinkedList<>(); + final Queue resourceCleanups = new ConcurrentLinkedQueue<>(); void add(SwiftInstanceCleanup cleanup) { resourceCleanups.add(cleanup); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index 3b6c4626..96353c37 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -31,7 +31,7 @@ public interface SwiftArena { void register(SwiftInstance instance); static ClosableSwiftArena ofConfined() { - return new ConfinedSwiftMemorySession(Thread.currentThread()); + return new ConfinedSwiftMemorySession(); } static SwiftArena ofAuto() { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index a093cc50..7eccde74 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -24,10 +24,10 @@ public final class SwiftLibraries { - // Library names of core Swift and SwiftKit + // Library names of core Swift and SwiftRuntimeFunctions public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; - public static final String LIB_NAME_SWIFTKITSWIFT = "SwiftKitSwift"; + public static final String LIB_NAME_SWIFT_RUNTIME_FUNCTIONS = "SwiftRuntimeFunctions"; /** * Allows for configuration if jextracted types should automatically attempt to load swiftCore and the library type is from. @@ -42,10 +42,10 @@ public final class SwiftLibraries { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); - if (loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_CORE); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); } return true; } diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index c04b1017..dcbbe8df 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -75,10 +75,10 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs.add("-Xlint:preview") } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -86,7 +86,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + args("build", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java index 08fad15b..b72818a3 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java @@ -24,7 +24,7 @@ public interface AllocatingSwiftArena extends SwiftArena, SegmentAllocator { MemorySegment allocate(long byteSize, long byteAlignment); static ClosableAllocatingSwiftArena ofConfined() { - return new FFMConfinedSwiftMemorySession(Thread.currentThread()); + return new FFMConfinedSwiftMemorySession(); } static AllocatingSwiftArena ofAuto() { diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java index 424e54b6..05daebd7 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java @@ -22,8 +22,8 @@ final class FFMConfinedSwiftMemorySession extends ConfinedSwiftMemorySession implements AllocatingSwiftArena, ClosableAllocatingSwiftArena { final Arena arena; - public FFMConfinedSwiftMemorySession(Thread owner) { - super(owner); + public FFMConfinedSwiftMemorySession() { + super(); this.arena = Arena.ofConfined(); } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index e03b9bfe..74a270c0 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -33,7 +33,7 @@ public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; - public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + public static final String SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME = "SwiftRuntimeFunctions"; private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -42,10 +42,10 @@ public class SwiftRuntime { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { System.loadLibrary(STDLIB_DYLIB_NAME); - if (loadSwiftKit) { - System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME); } return true; } @@ -487,4 +487,4 @@ boolean isEnabled() { } throw new IllegalArgumentException("Not handled log group: " + this); } -} \ No newline at end of file +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift new file mode 100644 index 00000000..5f488192 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -0,0 +1,348 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIAsyncTests { + + @Test("Import: async -> Void (Java, CompletableFuture)") + func completableFuture_asyncVoid_java() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func asyncVoid() async + * } + */ + public static java.util.concurrent.CompletableFuture asyncVoid() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$asyncVoid($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $asyncVoid(java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: async -> Void (Swift, CompletableFuture)") + func completableFuture_asyncVoid_swift() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + } + } + """ + ] + ) + } + + @Test("Import: async throws -> Void (Java, CompletableFuture)") + func completableFuture_asyncThrowsVoid_java() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async() async throws + * } + */ + public static java.util.concurrent.CompletableFuture async() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: async throws -> Void (Swift, CompletableFuture)") + func completableFuture_asyncThrowsVoid_swift() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } + } + } + } + """ + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (Java, CompletableFuture)") + func completableFuture_asyncIntToInt_java() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(i: Int64) async -> Int64 + * } + */ + public static java.util.concurrent.CompletableFuture async(long i) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(i, $future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(long i, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (Swift, CompletableFuture)") + func completableFuture_asyncIntToInt_swift() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try JavaVirtualMachine.shared().environment() + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try JavaVirtualMachine.shared().environment() + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + return + } + """ + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Java, CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_java() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(c: MyClass) async -> MyClass + * } + */ + public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena$) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(c.$memoryAddress(), $future); + return $future.thenApply((futureResult$) -> { + return MyClass.wrapMemoryAddressUnsafe(futureResult$, swiftArena$); + } + ); + } + """, + """ + private static native void $async(long c, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Swift, CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_swift() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong, result_future: jobject?) { + assert(c != 0, "c memory address was null") + let cBits$ = Int(Int64(fromJNI: c, in: environment)) + let c$ = UnsafeMutablePointer(bitPattern: cBits$) + guard let c$ else { + fatalError("c memory address was null in call to \\(#function)!") + } + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + return + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index a7527aa8..c3102a62 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -221,9 +221,9 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024init__JJ") func Java_com_example_swift_MyClass__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @@ -232,7 +232,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyClass.init()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, ] @@ -303,12 +303,12 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] @@ -352,7 +352,7 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024copy__J") func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") @@ -360,7 +360,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: self$.pointee.copy()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -404,18 +404,18 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024isEqual__JJ") func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { assert(other != 0, "other memory address was null") - let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) + let otherBits$ = Int(Int64(fromJNI: other, in: environment)) let other$ = UnsafeMutablePointer(bitPattern: otherBits$) guard let other$ else { fatalError("other memory address was null in call to \\(#function)!") } assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) + return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index b374d24e..b54749c1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -63,10 +63,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "()V")! let arguments$: [jvalue] = [] - environment!.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) } ) } @@ -115,10 +115,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! - let arguments$: [jvalue] = [_0.getJValue(in: environment!), _1.getJValue(in: environment!)] - return Int64(fromJNI: environment!.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment!) + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! + let arguments$: [jvalue] = [_0.getJValue(in: environment), _1.getJValue(in: environment)] + return Int64(fromJNI: environment.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index c6aaf923..ef422e42 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -224,25 +224,25 @@ struct JNIEnumTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyEnum.first) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2") func Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg0: jstring?) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment!))) + result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment!), y: Int32(fromJNI: y, in: environment!))) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ]) @@ -302,7 +302,7 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") let constructorID$ = cache$[method$] - return withVaList([_0.getJNIValue(in: environment!) ?? 0]) { + return withVaList([_0.getJNIValue(in: environment) ?? 0]) { return environment.interface.NewObjectV(environment, class$, constructorID$, $0) } } @@ -318,7 +318,7 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") let constructorID$ = cache$[method$] - return withVaList([x.getJNIValue(in: environment!), y.getJNIValue(in: environment!)]) { + return withVaList([x.getJNIValue(in: environment), y.getJNIValue(in: environment)]) { return environment.interface.NewObjectV(environment, class$, constructorID$, $0) } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index ac6b8384..69d77b73 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -71,7 +71,7 @@ struct JNIJavaKitTests { guard let javaInteger_unwrapped$ = javaInteger else { fatalError("javaInteger was null in call to \\(#function), but Swift requires non-optional!") } - SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment), int: Int64(fromJNI: int, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 27b0cdea..dddf1147 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -130,13 +130,13 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ") func Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { - return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment), i2: Int16(fromJNI: i2, in: environment), i3: Int32(fromJNI: i3, in: environment), i4: Int64(fromJNI: i4, in: environment)).getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD") func Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD(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!)) + SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment), f: Float(fromJNI: f, in: environment), d: Double(fromJNI: d, in: environment)) } """ ] @@ -179,7 +179,7 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { - return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.copy(String(fromJNI: string, in: environment)).getJNIValue(in: environment) } """, ] @@ -247,7 +247,7 @@ struct JNIModuleTests { @_cdecl("Java_com_example_swift_SwiftModule__00024methodB__") func Java_com_example_swift_SwiftModule__00024methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { do { - return try SwiftModule.methodB().getJNIValue(in: environment!) + return try SwiftModule.methodB().getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index be2e0f6a..6c931d7b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -72,10 +72,10 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { - let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment) : nil).map { Int64($0) << 32 | Int64(1) } ?? 0 - return result_value$.getJNIValue(in: environment!) + return result_value$.getJNIValue(in: environment) } """ ] @@ -123,8 +123,8 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B") func Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jstring?, result_discriminator$: jbyteArray?) -> jstring? { let result$: jstring? - if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment!) : nil) { - result$ = innerResult$.getJNIValue(in: environment!) + if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment) : nil) { + result$ = innerResult$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -180,14 +180,14 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalClass__J_3B") func Java_com_example_swift_SwiftModule__00024optionalClass__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, result_discriminator$: jbyteArray?) -> jlong { - let argBits$ = Int(Int64(fromJNI: arg, in: environment!)) + let argBits$ = Int(Int64(fromJNI: arg, in: environment)) let arg$ = UnsafeMutablePointer(bitPattern: argBits$) let result$: jlong if let innerResult$ = SwiftModule.optionalClass(arg$?.pointee) { let _result$ = UnsafeMutablePointer.allocate(capacity: 1) _result$.initialize(to: innerResult$) let _resultBits$ = Int64(Int(bitPattern: _result$)) - result$ = _resultBits$.getJNIValue(in: environment!) + result$ = _resultBits$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -242,7 +242,7 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2") func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { - return JavaLong(javaThis: $0, environment: environment!) + return JavaLong(javaThis: $0, environment: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index b5a0fcdb..c5302ca6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -107,11 +107,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ") func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -122,11 +122,11 @@ struct JNIProtocolTests { } let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) #endif - guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment!))) else { + guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment))) else { fatalError("y_typeMetadataAddress memory address was null") } let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self) - guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment!))) else { + guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment))) else { fatalError("y memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -172,11 +172,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ") func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) { - guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment!))) else { + guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment))) else { fatalError("s_typeMetadataAddress memory address was null") } let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self) - guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment!))) else { + guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment))) else { fatalError("s memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -222,11 +222,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ") func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index a7c689aa..f8830b64 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -142,9 +142,9 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024init__JJ") func Java_com_example_swift_MyStruct__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -214,12 +214,12 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index c9d313a5..363c117d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -70,7 +70,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.constant.getJNIValue(in: environment!) + return self$.pointee.constant.getJNIValue(in: environment) } """ ] @@ -135,7 +135,7 @@ struct JNIVariablesTests { fatalError("self memory address was null in call to \\(#function)!") } ... - return self$.pointee.mutable.getJNIValue(in: environment!) + return self$.pointee.mutable.getJNIValue(in: environment) } """, """ @@ -143,7 +143,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") ... - self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) + self$.pointee.mutable = Int64(fromJNI: newValue, in: environment) } """ ] @@ -188,7 +188,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.computed.getJNIValue(in: environment!) + return self$.pointee.computed.getJNIValue(in: environment) } """, ] @@ -234,7 +234,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... do { - return try self$.pointee.computedThrowing.getJNIValue(in: environment!) + return try self$.pointee.computedThrowing.getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue @@ -297,14 +297,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.getterAndSetter.getJNIValue(in: environment!) + return self$.pointee.getterAndSetter.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { ... - self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment) } """ ] @@ -363,14 +363,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.someBoolean.getJNIValue(in: environment!) + return self$.pointee.someBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment) } """ ] @@ -429,14 +429,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.isBoolean.getJNIValue(in: environment!) + return self$.pointee.isBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment) } """ ] From 1de727915763bb3101c6f4bc4e3bdcfc36b7fd99 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 1 Nov 2025 09:08:20 +0100 Subject: [PATCH 165/178] remove withVaList (#416) --- .../MySwiftLibrary/MySwiftLibrary.swift | 2 ++ ...wift2JavaGenerator+NativeTranslation.swift | 9 ++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 10 ++++------ .../JNI/JNIAsyncTests.swift | 20 ++++++++----------- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 10 ++++------ 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 09903638..e278326e 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -19,6 +19,8 @@ #if os(Linux) import Glibc +#elseif os(Android) + import Android #else import Darwin.C #endif diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 45208d7b..727294b1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -830,9 +830,12 @@ extension JNISwift2JavaGenerator { if swiftFunctionResultType.isVoid { printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") } else { - printer.printBraceBlock("withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)])") { printer in - printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0)") - } + printer.print( + """ + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) + """ + ) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 5403ae40..c9f8af9e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -251,16 +251,14 @@ extension JNISwift2JavaGenerator { """ ) let upcallArguments = zip(enumCase.parameterConversions, caseNames).map { conversion, caseName in - // '0' is treated the same as a null pointer. - let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? 0" : "" + let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? nil" : "" let result = conversion.native.conversion.render(&printer, caseName) - return "\(result)\(nullConversion)" + return "jvalue(\(conversion.native.javaType.jniFieldName): \(result)\(nullConversion))" } printer.print( """ - return withVaList([\(upcallArguments.joined(separator: ", "))]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [\(upcallArguments.joined(separator: ", "))] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) """ ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 5f488192..6b26c69b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -224,9 +224,8 @@ struct JNIAsyncTests { } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try JavaVirtualMachine.shared().environment() - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } else { @@ -238,9 +237,8 @@ struct JNIAsyncTests { } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try JavaVirtualMachine.shared().environment() - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } return @@ -317,9 +315,8 @@ struct JNIAsyncTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } else { @@ -334,9 +331,8 @@ struct JNIAsyncTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } return diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index ef422e42..38ef8789 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -302,9 +302,8 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") let constructorID$ = cache$[method$] - return withVaList([_0.getJNIValue(in: environment) ?? 0]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [jvalue(l: _0.getJNIValue(in: environment) ?? nil)] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """, """ @@ -318,9 +317,8 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") let constructorID$ = cache$[method$] - return withVaList([x.getJNIValue(in: environment), y.getJNIValue(in: environment)]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNIValue(in: environment)), jvalue(i: y.getJNIValue(in: environment))] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """ ]) From c58bbed1e3dac074df3ea26b32f118b85e45e14d Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 4 Nov 2025 02:01:02 +0100 Subject: [PATCH 166/178] [jextract] Propagate build plugin config to command (#415) Co-authored-by: Konrad `ktoso` Malawski --- Plugins/PluginsShared/PluginUtils.swift | 4 +- .../SwiftJavaExtractJNISampleApp/build.gradle | 3 +- Sources/JExtractSwiftLib/Swift2Java.swift | 4 +- .../Configuration.swift | 3 ++ .../JExtract/JExtractGenerationMode.swift | 4 ++ .../JExtractUnsignedIntegerMode.swift | 1 - .../Documentation.docc/SupportedFeatures.md | 6 +-- .../Commands/JExtractCommand.swift | 52 +++++++++++-------- .../SwiftJavaBaseAsyncParsableCommand.swift | 2 +- 9 files changed, 45 insertions(+), 34 deletions(-) diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 863cf99c..8278c645 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -66,12 +66,12 @@ extension PluginContext { .appending(path: "generated") .appending(path: "java") } - + var outputSwiftDirectory: URL { self.pluginWorkDirectoryURL .appending(path: "Sources") } - + func cachedClasspathFile(swiftModule: String) -> URL { self.pluginWorkDirectoryURL .appending(path: "\(swiftModule)", directoryHint: .notDirectory) diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index d92c96fb..b1aa8490 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,8 +102,7 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - // TODO: -v for debugging build issues... - args("build", "-v") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to 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", diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 19241592..ed76483e 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -95,8 +95,8 @@ public struct SwiftToJava { try translator.analyze() - switch config.mode { - case .some(.ffm), .none: + switch config.effectiveMode { + case .ffm: let generator = FFMSwift2JavaGenerator( config: self.config, translator: translator, diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 6d8d20e5..c9d0cedf 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -39,6 +39,9 @@ public struct Configuration: Codable { public var outputJavaDirectory: String? public var mode: JExtractGenerationMode? + public var effectiveMode: JExtractGenerationMode { + mode ?? .default + } public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift index 8df00cf0..1ad331da 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift @@ -19,4 +19,8 @@ public enum JExtractGenerationMode: String, Codable { /// Java Native Interface case jni + + public static var `default`: JExtractGenerationMode { + .ffm + } } diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift index d7a94070..b53a2c6b 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - /// Configures how Swift unsigned integers should be extracted by jextract. public enum JExtractUnsignedIntegerMode: String, Codable { /// Treat unsigned Swift integers as their signed equivalents in Java signatures, diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index e97ed03e..98dac732 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -135,10 +135,10 @@ on the Java side. | `Float` | `float` | | `Double` | `double` | -#### Unsigned numbers mode: wrap-guava +#### Unsigned numbers mode: wrapGuava You can configure `jextract` (in FFM mode) to instead import unsigned values as their unsigned type-safe representations -as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers wrap-guava` +as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers-mode wrapGuava` command line option, or set the corresponding configuration value in `swift-java.config` (TODO). This approach is type-safe, however it incurs a performance penalty for allocating a wrapper class for every @@ -162,7 +162,7 @@ you are expected to add a Guava dependency to your Java project. | `Float` | `float` | | `Double` | `double` | -> Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. +> Note: The `wrapGuava` mode is currently only available in FFM mode of jextract. ### Enums diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index aba79983..b5942d2a 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,14 +61,14 @@ extension SwiftJava { @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 - @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") - var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrapGuava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") + var unsignedNumbersMode: JExtractUnsignedIntegerMode? @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") - var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? - @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allow-automatic`, user can omit this parameter and a global GC-based arena will be used. `force-automatic` removes all explicit memory management.") - var memoryManagementMode: JExtractMemoryManagementMode = .default + @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allowGlobalAutomatic`, user can omit this parameter and a global GC-based arena will be used.") + var memoryManagementMode: JExtractMemoryManagementMode? @Option( help: """ @@ -80,7 +80,7 @@ extension SwiftJava { var dependsOn: [String] = [] @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") - var asyncFuncMode: JExtractAsyncFuncMode = .default + var asyncFuncMode: JExtractAsyncFuncMode? } } @@ -89,21 +89,21 @@ extension SwiftJava.JExtractCommand { if let javaPackage { config.javaPackage = javaPackage } - if let mode { - config.mode = mode - } else if config.mode == nil { - config.mode = .ffm - } + configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift - config.writeEmptyFiles = writeEmptyFiles - config.unsignedNumbersMode = unsignedNumbers - config.minimumInputAccessLevelMode = minimumInputAccessLevel - config.memoryManagementMode = memoryManagementMode - config.asyncFuncMode = asyncFuncMode - try checkModeCompatibility() + // @Flag does not support optional, so we check ourself if it is passed + let writeEmptyFiles = CommandLine.arguments.contains("--write-empty-files") ? true : nil + configure(&config.writeEmptyFiles, overrideWith: writeEmptyFiles) + + configure(&config.unsignedNumbersMode, overrideWith: self.unsignedNumbersMode) + configure(&config.minimumInputAccessLevelMode, overrideWith: self.minimumInputAccessLevelMode) + configure(&config.memoryManagementMode, overrideWith: self.memoryManagementMode) + configure(&config.asyncFuncMode, overrideWith: self.asyncFuncMode) + + try checkModeCompatibility(config: config) if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -112,7 +112,7 @@ extension SwiftJava.JExtractCommand { config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)" } - print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.mode ?? .ffm)".bold) + print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.effectiveMode)".bold) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) @@ -122,20 +122,26 @@ extension SwiftJava.JExtractCommand { } /// Check if the configured modes are compatible, and fail if not - func checkModeCompatibility() throws { - if self.mode == .jni { - switch self.unsignedNumbers { + func checkModeCompatibility(config: Configuration) throws { + if config.effectiveMode == .jni { + switch config.effectiveUnsignedNumbersMode { case .annotate: () // OK case .wrapGuava: throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") } - } else if self.mode == .ffm { - guard self.memoryManagementMode == .explicit else { + } else if config.effectiveMode == .ffm { + guard config.effectiveMemoryManagementMode == .explicit else { throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode)' memory management mode! \(Self.helpMessage)") } } } + + func configure(_ setting: inout T?, overrideWith value: T?) { + if let value { + setting = value + } + } } struct IncompatibleModeError: Error { diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 25b162e1..6b8c0ec6 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -167,4 +167,4 @@ extension SwiftJavaBaseAsyncParsableCommand { config.logLevel = command.logLevel return config } -} \ No newline at end of file +} From cc0ff8f6e478a05079c7206d7d4014d9c6eef776 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 4 Nov 2025 15:53:28 +0900 Subject: [PATCH 167/178] Adjust Task.immediate use so we can bring back Swift 6.1 CI until 6.2 is fixed (#417) --- .github/workflows/pull_request.yml | 4 +- Package.swift | 2 +- .../Package.swift | 2 +- Sources/JExtractSwiftLib/CodePrinter.swift | 14 ++ ...wift2JavaGenerator+NativeTranslation.swift | 24 ++-- .../Asserts/TextAssertions.swift | 22 ++-- .../JNI/JNIAsyncTests.swift | 122 ++++++++++-------- docker/Dockerfile | 2 +- 8 files changed, 110 insertions(+), 82 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5e5b767a..70cff51c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -140,7 +140,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -182,7 +182,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names diff --git a/Package.swift b/Package.swift index 09743135..7ac7d1f9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.2 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index 33a35c49..6343e0db 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.2 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index c5f04d51..e6498683 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -70,6 +70,20 @@ public struct CodePrinter { } } + public mutating func printHashIfBlock( + _ header: Any, + function: String = #function, + file: String = #fileID, + line: UInt = #line, + body: (inout CodePrinter) throws -> () + ) rethrows { + print("#if \(header)") + indent() + try body(&self) + outdent() + print("#endif // end of \(header)", .sloc, function: function, file: file, line: line) + } + public mutating func printBraceBlock( _ header: Any, function: String = #function, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 727294b1..d4ce9426 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -825,7 +825,7 @@ extension JNISwift2JavaGenerator { func printDo(printer: inout CodePrinter) { printer.print("let swiftResult$ = await \(placeholder)") - printer.print("environment = try JavaVirtualMachine.shared().environment()") + printer.print("environment = try! JavaVirtualMachine.shared().environment()") let inner = inner.render(&printer, "swiftResult$") if swiftFunctionResultType.isVoid { printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") @@ -839,7 +839,7 @@ extension JNISwift2JavaGenerator { } } - func printTask(printer: inout CodePrinter) { + func printTaskBody(printer: inout CodePrinter) { printer.printBraceBlock("defer") { printer in // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") @@ -867,18 +867,22 @@ extension JNISwift2JavaGenerator { } } - printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in - printer.printBraceBlock("Task.immediate") { printer in - // Immediate runs on the caller thread, so we don't need to attach the environment again. - printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. - printTask(printer: &printer) + printer.print("var task: Task? = nil") + printer.printHashIfBlock("swift(>=6.2)") { printer in + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("task = Task.immediate") { printer in + // Immediate runs on the caller thread, so we don't need to attach the environment again. + printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. + printTaskBody(printer: &printer) + } } } - printer.printBraceBlock("else") { printer in - printer.printBraceBlock("Task") { printer in + + printer.printBraceBlock("if task == nil") { printer in + printer.printBraceBlock("task = Task") { printer in // We can be on any thread, so we need to attach the thread. printer.print("var environment = try! JavaVirtualMachine.shared().environment()") - printTask(printer: &printer) + printTaskBody(printer: &printer) } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 001d34fa..67671548 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -179,7 +179,9 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(n): \(e)".yellow(if: diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n))) + let isMismatch = diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n) + let marker = isMismatch ? " // <<<<<<<<<<< mismatch" : "" + print("\(n): \(e)\(marker)".yellow(if: isMismatch)) } } @@ -188,15 +190,7 @@ func assertOutput( let printFromLineNo = matchingOutputOffset for (n, g) in gotLines.enumerated() where n >= printFromLineNo { let baseLine = "\(n): \(g)" - var line = baseLine - if diffLineNumbers.contains(n) { - line += "\n" - let leadingCount = "\(n): ".count - let message = "\(String(repeating: " ", count: leadingCount))\(String(repeating: "^", count: 8)) EXPECTED MATCH OR SEARCHING FROM HERE " - line += "\(message)\(String(repeating: "^", count: max(0, line.count - message.count)))" - line = line.red - } - print(line) + print(baseLine) } print("==== ---------------------------------------------------------------\n") } @@ -248,14 +242,18 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(e)".yellow(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? " // <<<<<<<< error: mismatch" : "" + print("\(e)\(marker)".yellow(if: isMismatch)) } } print("==== ---------------------------------------------------------------") print("Got output:") for (n, g) in gotLines.enumerated() { - print("\(g)".red(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? "// <<<<<<<< error: mismatch" : "" + print("\(g)\(marker)".red(if: isMismatch)) } print("==== ---------------------------------------------------------------\n") } diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 6b26c69b..82922c7d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -59,27 +59,30 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } - let swiftResult$ = await SwiftModule.asyncVoid() - environment = try JavaVirtualMachine.shared().environment() - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } } @@ -130,27 +133,30 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) - } - do { - let swiftResult$ = await try SwiftModule.async() - environment = try JavaVirtualMachine.shared().environment() - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) - } - catch { - let catchEnvironment = try! JavaVirtualMachine.shared().environment() - let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) - catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } } } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() @@ -158,7 +164,7 @@ struct JNIAsyncTests { } do { let swiftResult$ = await try SwiftModule.async() - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } catch { @@ -215,28 +221,31 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + var task: Task? = nil + #if swift(>=6.2) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { + task = Task.immediate { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } - else { - Task { + #endif // end of swift(>=6.2) + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } @@ -303,31 +312,34 @@ struct JNIAsyncTests { fatalError("c memory address was null in call to \\(#function)!") } let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try! JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } - let swiftResult$ = await SwiftModule.async(c: c$.pointee) - environment = try JavaVirtualMachine.shared().environment() - let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: swiftResult$) - let resultBits$ = Int64(Int(bitPattern: result$)) - let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1109dab3..ef428b87 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,10 +21,10 @@ ENV LANGUAGE=en_US.UTF-8 # JDK dependency RUN curl -s "https://get.sdkman.io" | bash RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 25.0.1-amzn" +ENV JAVA_HOME="$(sdk home java current)" RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ tar zxf swiftly-$(uname -m).tar.gz && \ ./swiftly init --quiet-shell-followup --assume-yes && \ . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \ hash -r - From b4706f3f744de3b27cef892af580ed11419811f2 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 5 Nov 2025 11:51:16 +0900 Subject: [PATCH 168/178] Disable swift-syntax prebuilts (primarily on linux) (#422) --- .github/workflows/pull_request.yml | 8 ++++---- Samples/JavaDependencySampleApp/ci-validate.sh | 6 +++++- Samples/JavaProbablyPrime/ci-validate.sh | 5 ++++- Samples/SwiftAndJavaJarSampleLib/build.gradle | 3 ++- Samples/SwiftJavaExtractFFMSampleApp/build.gradle | 3 ++- Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh | 2 +- Samples/SwiftJavaExtractJNISampleApp/build.gradle | 3 ++- Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh | 2 +- SwiftKitCore/build.gradle | 3 ++- SwiftKitFFM/build.gradle | 3 ++- 10 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 70cff51c..16151168 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -38,7 +38,7 @@ jobs: - name: Prepare CI Environment uses: ./.github/actions/prepare_env - name: Swift Build - run: swift build + run: swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - name: Run documentation check run: ./.github/scripts/validate_docs.sh @@ -132,7 +132,7 @@ jobs: - name: Install jemalloc run: apt-get update && apt-get install -y libjemalloc-dev - name: Swift Benchmarks - run: swift package --package-path Benchmarks/ benchmark + run: swift package --package-path Benchmarks/ --disable-experimental-prebuilts benchmark # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 test-swift: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) @@ -152,9 +152,9 @@ jobs: - name: Prepare CI Environment uses: ./.github/actions/prepare_env - name: Swift Build - run: "swift build --build-tests --disable-sandbox" + run: swift build --build-tests --disable-sandbox --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - name: Swift Test - run: "swift test" + run: swift test --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 test-swift-macos: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index 2758e6aa..b6162fdc 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -8,7 +8,11 @@ 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 \ + +# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +swift run \ + --disable-experimental-prebuilts \ + swift-java resolve \ Sources/JavaCommonsCSV/swift-java.config \ --swift-module JavaCommonsCSV \ --output-directory .build/plugins/outputs/javadependencysampleapp/JavaCommonsCSV/destination/SwiftJavaPlugin/ diff --git a/Samples/JavaProbablyPrime/ci-validate.sh b/Samples/JavaProbablyPrime/ci-validate.sh index 0bdd86d1..dc624996 100755 --- a/Samples/JavaProbablyPrime/ci-validate.sh +++ b/Samples/JavaProbablyPrime/ci-validate.sh @@ -3,4 +3,7 @@ set -e set -x -swift run JavaProbablyPrime 1337 \ No newline at end of file +# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +swift run \ + --disable-experimental-prebuilts \ + JavaProbablyPrime 1337 \ No newline at end of file diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index 3ad59fe5..e6404adb 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -116,7 +116,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // 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", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle index 0200f234..dcfacdd3 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle +++ b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle @@ -101,7 +101,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // 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", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh index 8758bbee..ff5c32c8 100755 --- a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh @@ -3,7 +3,7 @@ set -x set -e -swift build # as a workaround for building swift build from within gradle having issues on CI sometimes +swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index b1aa8490..7f30ee42 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,7 +102,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // 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", diff --git a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh index 8758bbee..ff5c32c8 100755 --- a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh @@ -3,7 +3,7 @@ set -x set -e -swift build # as a workaround for building swift build from within gradle having issues on CI sometimes +swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 ./gradlew run ./gradlew test \ No newline at end of file diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index f4376e94..bc6bd279 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -84,7 +84,8 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftRuntimeFunctions") + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index dcbbe8df..08a36a3e 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -86,7 +86,8 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftRuntimeFunctions") + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") From a9f4f07a94acc8af07ba6c118072235d8ed70054 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 5 Nov 2025 23:08:00 +0100 Subject: [PATCH 169/178] fix docs about init and add tests (#426) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 8 ++++++++ .../Sources/MySwiftLibrary/MySwiftStruct.swift | 8 ++++++++ .../java/com/example/swift/MySwiftClassTest.java | 11 +++++++++++ .../java/com/example/swift/MySwiftStructTest.java | 13 +++++++++++++ .../Documentation.docc/SupportedFeatures.md | 2 +- 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 4b334a5e..e6aed287 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -59,6 +59,14 @@ public class MySwiftClass { self.y = 5 } + convenience public init(throwing: Bool) throws { + if throwing { + throw MySwiftError.swiftError + } else { + self.init() + } + } + deinit { p("deinit called!") } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift index 00e2533c..ddd77132 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -21,6 +21,14 @@ public struct MySwiftStruct { self.len = len } + public init?(doInit: Bool) { + if doInit { + self.init(cap: 10, len: 10) + } else { + return nil + } + } + public func getCapacity() -> Int64 { self.cap } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index fba8f13f..a5838629 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -40,6 +40,17 @@ void init_withParameters() { } } + @Test + void init_throwing() { + try (var arena = SwiftArena.ofConfined()) { + Exception exception = assertThrows(Exception.class, () -> MySwiftClass.init(true, arena)); + assertEquals("swiftError", exception.getMessage()); + + MySwiftClass c = assertDoesNotThrow(() -> MySwiftClass.init(false, arena)); + assertNotNull(c); + } + } + @Test void sum() { try (var arena = SwiftArena.ofConfined()) { diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java index c2c1170b..e52e1959 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -18,6 +18,8 @@ import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import org.swift.swiftkit.core.SwiftArena; +import java.util.Optional; + import static org.junit.jupiter.api.Assertions.*; public class MySwiftStructTest { @@ -30,6 +32,17 @@ void init() { } } + @Test + void init_optional() { + try (var arena = SwiftArena.ofConfined()) { + assertEquals(Optional.empty(), MySwiftStruct.init(false, arena)); + + Optional optionalStruct = MySwiftStruct.init(true, arena); + assertTrue(optionalStruct.isPresent()); + assertEquals(10, optionalStruct.get().getLen()); + } + } + @Test void getAndSetLen() { try (var arena = SwiftArena.ofConfined()) { diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 98dac732..4bbaee40 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -47,7 +47,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Swift Feature | FFM | JNI | |--------------------------------------------------------------------------------------|----------|-----| | Initializers: `class`, `struct` | ✅ | ✅ | -| Optional Initializers / Throwing Initializers | ❌ | ❌ | +| Optional Initializers / Throwing Initializers | ❌ | ✅ | | Deinitializers: `class`, `struct` | ✅ | ✅ | | `enum` | ❌ | ✅ | | `actor` | ❌ | ❌ | From 3ebdfbb309c327c3dae87ecf345ec4c53cd5d509 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 Nov 2025 10:45:47 +0100 Subject: [PATCH 170/178] Add CI to build JNI Sample for Android (#427) Co-authored-by: Konrad `ktoso` Malawski --- .github/workflows/pull_request.yml | 24 +++++++++++++++++++ .../SwiftJavaExtractJNISampleApp/build.gradle | 13 ++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 16151168..a0f2eda5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -176,6 +176,30 @@ jobs: - name: Swift Test run: "swift test" + build-swift-android: + name: Sample SwiftJavaExtractJNISampleApp (Android) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} android:${{matrix.sdk_triple}}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift_version: ['nightly-main'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + sdk_triple: ['x86_64-unknown-linux-android28'] + ndk_version: ['r27d'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install Swift SDK for Android and build + run: | + apt-get -q update && apt-get -yq install curl + cd Samples/SwiftJavaExtractJNISampleApp + curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/install-and-build-with-sdk.sh | \ + bash -s -- --android --build-command="swift build" --android-sdk-triple="${{ matrix.sdk_triple }}" --android-ndk-version="${{ matrix.ndk_version }}" ${{ matrix.swift_version }} + verify-samples: name: Sample ${{ matrix.sample_app }} (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index 7f30ee42..a8ce51d2 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -100,10 +100,19 @@ def jextract = tasks.register("jextract", Exec) { } } + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + def cmdArgs = ["build", "--disable-experimental-prebuilts"] + + // Check if the 'swiftSdk' project property was passed + if (project.hasProperty('swiftSdk')) { + // If it was, add the --sdk argument and its value + cmdArgs.add("--swift-sdk") + cmdArgs.add(project.property('swiftSdk').toString()) + } + workingDir = layout.projectDirectory commandLine "swift" - // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - args("build", "--disable-experimental-prebuilts") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + args(cmdArgs) // 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", From 79f1f8e0f336806362bcdc6e090ff7afa968f504 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 6 Nov 2025 18:56:56 +0900 Subject: [PATCH 171/178] Enhance README with simple introduction, presentation link Updated project description and added introduction section with a reference to a WWDC presentation. --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f9a5fde..abca3b9d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # Swift Java Interoperability Tools and Libraries -This repository contains two approaches to Swift/Java interoperability. +This project contains tools and libraries that facilitate **Swift & Java Interoperability**. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. +## Introduction + +If you'd like to check out a quick introduction to Swift & Java interoperability, you may be interested in this presentation from WWDC25: [WWDC25: Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA). + +While we work on more quickstarts and documentation, please refer to the Sample projects located in [Samples/](Samples/) that showcase the various ways you can use swift-java in your Swift or Java projects. + ## Dependencies ### Required JDK versions From cc728395a68bdc89d24250ac8f1979e1fe8b853b Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 7 Nov 2025 06:50:54 +0100 Subject: [PATCH 172/178] [jextract] fix support for objects in async (#428) Co-authored-by: Konrad `ktoso` Malawski --- .../Sources/MySwiftLibrary/Async.swift | 4 ++ .../java/com/example/swift/AsyncTest.java | 6 ++ .../Convenience/JavaType+Extensions.swift | 9 +++ ...ISwift2JavaGenerator+JavaTranslation.swift | 6 +- ...wift2JavaGenerator+NativeTranslation.swift | 41 ++++++++------ Sources/JExtractSwiftLib/JavaParameter.swift | 9 +++ .../JNI/JNIAsyncTests.swift | 55 +++++++++++++++++++ 7 files changed, 109 insertions(+), 21 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index ebef1892..99f3a393 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -36,3 +36,7 @@ public func asyncOptional(i: Int64) async throws -> Int64? { public func asyncThrows() async throws { throw MySwiftError.swiftError } + +public func asyncString(input: String) async -> String { + return input +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java index ae6e7cc7..5fe7c131 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -74,4 +74,10 @@ void asyncOptional() { CompletableFuture future = MySwiftLibrary.asyncOptional(42); assertEquals(OptionalLong.of(42), future.join()); } + + @Test + void asyncString() { + CompletableFuture future = MySwiftLibrary.asyncString("hey"); + assertEquals("hey", future.join()); + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 3b29fcd3..645e5aa4 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -137,4 +137,13 @@ extension JavaType { return self } } + + var requiresBoxing: Bool { + switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double: + true + default: + false + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 56174e3e..a4e5ab45 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -514,14 +514,12 @@ extension JNISwift2JavaGenerator { ) // Update native function - nativeFunctionSignature.result.javaType = .void nativeFunctionSignature.result.conversion = .asyncCompleteFuture( - nativeFunctionSignature.result.conversion, swiftFunctionResultType: originalFunctionSignature.result.type, - nativeReturnType: nativeFunctionSignature.result.javaType, - outParameters: nativeFunctionSignature.result.outParameters, + nativeFunctionSignature: nativeFunctionSignature, isThrowing: originalFunctionSignature.isThrowing ) + nativeFunctionSignature.result.javaType = .void nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index d4ce9426..13e8c676 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -570,10 +570,8 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) indirect case asyncCompleteFuture( - NativeSwiftConversionStep, swiftFunctionResultType: SwiftType, - nativeReturnType: JavaType, - outParameters: [JavaParameter], + nativeFunctionSignature: NativeFunctionSignature, isThrowing: Bool ) @@ -806,15 +804,22 @@ extension JNISwift2JavaGenerator { return unwrappedName case .asyncCompleteFuture( - let inner, let swiftFunctionResultType, - let nativeReturnType, - let outParameters, + let nativeFunctionSignature, let isThrowing ): + var globalRefs: [String] = ["globalFuture"] + // Global ref all indirect returns - for outParameter in outParameters { + for outParameter in nativeFunctionSignature.result.outParameters { printer.print("let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))") + globalRefs.append(outParameter.name) + } + + // We also need to global ref any objects passed in + for parameter in nativeFunctionSignature.parameters.flatMap(\.parameters) where !parameter.type.isPrimitive { + printer.print("let \(parameter.name) = environment.interface.NewGlobalRef(environment, \(parameter.name))") + globalRefs.append(parameter.name) } printer.print( @@ -826,16 +831,19 @@ extension JNISwift2JavaGenerator { func printDo(printer: inout CodePrinter) { printer.print("let swiftResult$ = await \(placeholder)") printer.print("environment = try! JavaVirtualMachine.shared().environment()") - let inner = inner.render(&printer, "swiftResult$") + let inner = nativeFunctionSignature.result.conversion.render(&printer, "swiftResult$") if swiftFunctionResultType.isVoid { printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") } else { - printer.print( - """ - let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment) - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) - """ - ) + let result: String + if nativeFunctionSignature.result.javaType.requiresBoxing { + printer.print("let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)") + result = "boxedResult$" + } else { + result = inner + } + + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: \(result))])") } } @@ -843,9 +851,8 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock("defer") { printer in // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") - printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)") - for outParameter in outParameters { - printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(outParameter.name))") + for globalRef in globalRefs { + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") } } if isThrowing { diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 2670e6ee..0b243b3a 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -35,6 +35,15 @@ struct JavaParameter { } } + var isPrimitive: Bool { + switch self { + case .concrete(let javaType): + javaType.isPrimitive + case .generic(let name, let extends): + false + } + } + var jniTypeName: String { switch self { case .concrete(let type): type.jniTypeName diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 82922c7d..975cccc6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -353,4 +353,59 @@ struct JNIAsyncTests { ] ) } + + @Test("Import: (String) async -> String (Java, CompletableFuture)") + func completableFuture_asyncStringToString_java() throws { + try assertOutput( + input: """ + public func async(s: String) async -> String + """, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static java.util.concurrent.CompletableFuture async(java.lang.String s) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(s, $future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(java.lang.String s, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (String) async -> String (Swift, CompletableFuture)") + func completableFuture_asyncStringToString_swift() throws { + try assertOutput( + input: """ + public func async(s: String) async -> String + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, s: jstring?, result_future: jobject?) { + let s = environment.interface.NewGlobalRef(environment, s) + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + ... + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + environment.interface.DeleteGlobalRef(deferEnvironment, s) + } + ... + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: swiftResult$.getJNIValue(in: environment))]) + ... + } + """ + ] + ) + } } From cee5e020ac29444d59ebb29afb246ac3cad6cf30 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 7 Nov 2025 14:51:28 +0900 Subject: [PATCH 173/178] Start replacing ad-hoc logging with swift-log adoption (#421) --- Package.swift | 3 +++ .../Commands/ConfigureCommand.swift | 6 ++++- .../Commands/WrapJavaCommand.swift | 7 ++++-- Sources/SwiftJavaTool/CommonOptions.swift | 25 ++++++++++--------- .../SwiftJavaBaseAsyncParsableCommand.swift | 20 ++++++++++----- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/Package.swift b/Package.swift index 7ac7d1f9..722859ee 100644 --- a/Package.swift +++ b/Package.swift @@ -205,6 +205,7 @@ let package = Package( .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .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"), + .package(url: "https://github.com/apple/swift-log", from: "1.2.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 @@ -395,6 +396,7 @@ let package = Package( .target( name: "SwiftJavaToolLib", dependencies: [ + .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), @@ -418,6 +420,7 @@ let package = Package( .executableTarget( name: "SwiftJavaTool", dependencies: [ + .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 3bce6c18..9a05a286 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import Logging import ArgumentParser import Foundation import SwiftJavaToolLib @@ -27,6 +28,9 @@ import SwiftJavaShared extension SwiftJava { struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + + static let log: Logging.Logger = Logger(label: "swift-java:\(configuration.commandName!)") + static let configuration = CommandConfiguration( commandName: "configure", abstract: "Configure and emit a swift-java.config file based on an input dependency or jar file") @@ -63,7 +67,7 @@ extension SwiftJava { extension SwiftJava.ConfigureCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { let classpathEntries = self.configureCommandJVMClasspath( - searchDirs: [self.effectiveSwiftModuleURL], config: config) + searchDirs: [self.effectiveSwiftModuleURL], config: config, log: Self.log) let jvm = try self.makeJVM(classpathEntries: classpathEntries) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 69ad3ebe..43362edb 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -14,15 +14,18 @@ import Foundation import ArgumentParser +import Logging import SwiftJavaToolLib import SwiftJava import JavaUtilJar -import SwiftJavaToolLib import SwiftJavaConfigurationShared extension SwiftJava { struct WrapJavaCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + + static let log: Logging.Logger = .init(label: "swift-java:\(configuration.commandName!)") + static let configuration = CommandConfiguration( commandName: "wrap-java", abstract: "Wrap Java classes with corresponding Swift bindings.") @@ -74,7 +77,7 @@ extension SwiftJava.WrapJavaCommand { print("[trace][swift-java] INPUT: \(input)") var classpathEntries = self.configureCommandJVMClasspath( - searchDirs: classpathSearchDirs, config: config) + searchDirs: classpathSearchDirs, config: config, log: Self.log) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn).map { moduleName, config in diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index c88ecbc8..e8aee62d 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -20,6 +20,7 @@ import SwiftJava import JavaUtilJar import JavaNet import SwiftSyntax +import Logging import SwiftJavaConfigurationShared import SwiftJavaShared @@ -43,7 +44,7 @@ extension SwiftJava { var inputSwift: String? = nil @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") - var logLevel: Logger.Level = .info + var logLevel: JExtractSwiftLib.Logger.Level = .info } struct CommonJVMOptions: ParsableArguments { @@ -78,46 +79,46 @@ extension HasCommonJVMOptions { /// swift-java.classpath files as configured. /// Parameters: /// - searchDirs: search directories where we can find swift.java.classpath files to include in the configuration - func configureCommandJVMClasspath(searchDirs: [Foundation.URL], config: Configuration) -> [String] { + func configureCommandJVMClasspath(searchDirs: [Foundation.URL], config: Configuration, log: Logging.Logger) -> [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)") + log.debug("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)") + log.debug("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)") + log.debug("Search *.swift-java.classpath in: \(classPathFilesSearchDirectory)") let foundSwiftJavaClasspath = findSwiftJavaClasspaths(in: classPathFilesSearchDirectory) - print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)") + log.debug("Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)") classpathEntries += foundSwiftJavaClasspath } if !classpathOptionEntries.isEmpty { - print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)") + log.debug("Classpath from options: \(classpathOptionEntries)") classpathEntries += classpathOptionEntries } else { // * Base classpath from CLASSPATH env variable - print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)") + log.debug("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)") + log.debug("Extra classpath: \(extraClasspathEntries)") classpathEntries += extraClasspathEntries // Bring up the Java VM when necessary - // if logLevel >= .debug { + if log.logLevel >= .debug { let classpathString = classpathEntries.joined(separator: ":") - print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)") - // } + log.debug("Initialize JVM with classpath: \(classpathString)") + } return classpathEntries } diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 6b8c0ec6..9763b4b3 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -24,9 +24,13 @@ import SwiftSyntax import SwiftSyntaxBuilder import SwiftJavaConfigurationShared import SwiftJavaShared +import Logging protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { - var logLevel: Logger.Level { get set } + + var log: Logging.Logger { get } + + var logLevel: JExtractSwiftLib.Logger.Level { get set } /// Must be implemented with an `@OptionGroup` in Command implementations var commonOptions: SwiftJava.CommonOptions { get set } @@ -45,8 +49,8 @@ extension SwiftJavaBaseAsyncParsableCommand { extension SwiftJavaBaseAsyncParsableCommand { public mutating func run() async { - print("[info][swift-java] Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))") - print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: ".").path)") + self.log.info("Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))") + self.log.info("Current work directory: \(URL(fileURLWithPath: ".").path)") do { var config = try readInitialConfiguration(command: self) @@ -54,12 +58,12 @@ extension SwiftJavaBaseAsyncParsableCommand { } 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)") + self.log.error("\(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) + self.log.debug("\("Done: ".green) \(CommandLine.arguments.joined(separator: " ").green)") } } @@ -95,7 +99,11 @@ extension SwiftJavaBaseAsyncParsableCommand { extension SwiftJavaBaseAsyncParsableCommand { - var logLevel: Logger.Level { + var log: Logging.Logger { // FIXME: replace with stored property inside specific commands + .init(label: "swift-java") + } + + var logLevel: JExtractSwiftLib.Logger.Level { get { self.commonOptions.logLevel } From cafcc1d5c0e45bc31f73087596c1fe521eb57c94 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 7 Nov 2025 11:02:10 +0100 Subject: [PATCH 174/178] [jextract/jni] Add support for importing nested types (#429) --- .../Sources/MySwiftLibrary/NestedTypes.swift | 47 ++++++ .../com/example/swift/NestedTypesTest.java | 45 ++++++ Sources/JExtractSwiftLib/ImportedDecls.swift | 2 + Sources/JExtractSwiftLib/JNI/JNICaching.swift | 8 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 ++- ...ISwift2JavaGenerator+JavaTranslation.swift | 6 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 6 +- .../SwiftTypes/SwiftModuleSymbolTable.swift | 2 +- .../SwiftTypes/SwiftTypeLookupContext.swift | 10 +- .../Documentation.docc/SupportedFeatures.md | 2 +- .../JNI/JNINestedTypesTests.swift | 137 ++++++++++++++++++ 11 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift new file mode 100644 index 00000000..b7edefa9 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +public class A { + public init() {} + + public class B { + public init() {} + + public struct C { + public init() {} + + public func g(a: A, b: B, bbc: BB.C) {} + } + } + + public class BB { + public init() {} + + public struct C { + public init() {} + } + } + + public func f(a: A, b: A.B, c: A.B.C, bb: BB, bbc: BB.C) {} +} + +public enum NestedEnum { + case one(OneStruct) + + public struct OneStruct { + public init() {} + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java new file mode 100644 index 00000000..b006ea91 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class NestedTypesTest { + @Test + void testClassesAndStructs() { + try (var arena = SwiftArena.ofConfined()) { + var a = A.init(arena); + var b = A.B.init(arena); + var c = A.B.C.init(arena); + var bb = A.BB.init(arena); + var abbc = A.BB.C.init(arena); + + a.f(a, b, c, bb, abbc); + c.g(a, b, abbc); + } + } + + @Test + void testStructInEnum() { + try (var arena = SwiftArena.ofConfined()) { + var obj = NestedEnum.one(NestedEnum.OneStruct.init(arena), arena); + var one = obj.getAsOne(arena); + assertTrue(one.isPresent()); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 84cc43c0..9ba1cd6f 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -41,12 +41,14 @@ package final class ImportedNominalType: ImportedDecl { package var variables: [ImportedFunc] = [] package var cases: [ImportedEnumCase] = [] var inheritedTypes: [SwiftType] + package var parent: SwiftNominalTypeDeclaration? init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { self.swiftNominal = swiftNominal self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap { try? SwiftType($0.type, lookupContext: lookupContext) } ?? [] + self.parent = swiftNominal.parent } var swiftType: SwiftType { diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift index cdb13e3f..b0521946 100644 --- a/Sources/JExtractSwiftLib/JNI/JNICaching.swift +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -14,15 +14,15 @@ enum JNICaching { static func cacheName(for type: ImportedNominalType) -> String { - cacheName(for: type.swiftNominal.name) + cacheName(for: type.swiftNominal.qualifiedName) } static func cacheName(for type: SwiftNominalType) -> String { - cacheName(for: type.nominalTypeDecl.name) + cacheName(for: type.nominalTypeDecl.qualifiedName) } - private static func cacheName(for name: String) -> String { - "_JNI_\(name)" + private static func cacheName(for qualifiedName: String) -> String { + "_JNI_\(qualifiedName.replacingOccurrences(of: ".", with: "_"))" } static func cacheMemberName(for enumCase: ImportedEnumCase) -> String { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ce6ec18e..1e59c196 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -40,7 +40,9 @@ extension JNISwift2JavaGenerator { package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) - for (_, ty) in importedTypes { + // Each parent type goes into its own file + // any nested types are printed inside the body as `static class` + for (_, ty) in importedTypes.filter({ _, type in type.parent == nil }) { let filename = "\(ty.swiftNominal.name).java" logger.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) @@ -145,6 +147,15 @@ extension JNISwift2JavaGenerator { """ ) + let nestedTypes = self.analysis.importedTypes.filter { _, type in + type.parent == decl.swiftNominal + } + + for nestedType in nestedTypes { + printConcreteType(&printer, nestedType.value) + printer.println() + } + printer.print( """ /** @@ -255,13 +266,18 @@ extension JNISwift2JavaGenerator { if decl.swiftNominal.isSendable { printer.print("@ThreadSafe // Sendable") } + var modifiers = ["public"] + if decl.parent != nil { + modifiers.append("static") + } + modifiers.append("final") var implements = ["JNISwiftInstance"] implements += decl.inheritedTypes .compactMap(\.asNominalTypeDeclaration) .filter { $0.kind == .protocol } .map(\.name) let implementsClause = implements.joined(separator: ", ") - printer.printBraceBlock("public final class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in + printer.printBraceBlock("\(modifiers.joined(separator: " ")) class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in body(&printer) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a4e5ab45..d9a8f8d9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -307,9 +307,7 @@ extension JNISwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> [TranslatedParameter] { - try parameters.enumerated().map { - idx, - param in + try parameters.enumerated().map { idx, param in let parameterName = param.name ?? "arg\(idx)" return try translateParameter( swiftType: param.type, @@ -373,7 +371,7 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.name + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName if let knownType = nominalType.nominalTypeDecl.knownTypeKind { switch knownType { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c9f8af9e..91a0b0e7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -430,7 +430,7 @@ extension JNISwift2JavaGenerator { let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") - + "_\(parentName.escapedJNIIdentifier)_" + + "_\(parentName.replacingOccurrences(of: ".", with: "$").escapedJNIIdentifier)_" + javaMethodName.escapedJNIIdentifier + "__" + jniSignature.escapedJNIIdentifier @@ -474,7 +474,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$typeMetadataAddressDowncall", - parentName: type.swiftNominal.name, + parentName: type.swiftNominal.qualifiedName, parameters: [], resultType: .long ) { printer in @@ -493,7 +493,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$destroy", - parentName: type.swiftNominal.name, + parentName: type.swiftNominal.qualifiedName, parameters: [ selfPointerParam ], diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift index 6328045f..2db19928 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift @@ -56,4 +56,4 @@ extension SwiftModuleSymbolTable { /// Names of modules which are alternative for currently checked module. let moduleNames: Set } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 13f42cfc..33759a2c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -119,7 +119,15 @@ class SwiftTypeLookupContext { /// Create a nominal type declaration instance for the specified syntax node. private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode, sourceFilePath: String) throws -> SwiftNominalTypeDeclaration { - SwiftNominalTypeDeclaration( + + if let symbolTableDeclaration = self.symbolTable.lookupType( + node.name.text, + parent: try parentTypeDecl(for: node) + ) { + return symbolTableDeclaration + } + + return SwiftNominalTypeDeclaration( sourceFilePath: sourceFilePath, moduleName: self.symbolTable.moduleName, parent: try parentTypeDecl(for: node), diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 4bbaee40..3178f60d 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -86,7 +86,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Subscripts: `subscript()` | ❌ | ❌ | | Equatable | ❌ | ❌ | | Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | -| Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | +| Nested types: `struct Hello { struct World {} }` | ❌ | ✅ | | Inheritance: `class Caplin: Capybara` | ❌ | ❌ | | Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | | Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift new file mode 100644 index 00000000..50a0ae26 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNINestedTypesTests { + let source1 = """ + public class A { + public class B { + public func g(c: C) {} + + public struct C { + public func h(b: B) {} + } + } + } + + public func f(a: A, b: A.B, c: A.B.C) {} + """ + + @Test("Import: class and struct A.B.C (Java)") + func nestedClassesAndStructs_java() throws { + try assertOutput( + input: source1, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class A implements JNISwiftInstance { + ... + public static final class B implements JNISwiftInstance { + ... + public static final class C implements JNISwiftInstance { + ... + public void h(A.B b) { + ... + } + ... + public void g(A.B.C c) { + ... + } + ... + } + """, + """ + public static void f(A a, A.B b, A.B.C c) { + ... + } + ... + """ + ] + ) + } + + @Test("Import: class and struct A.B.C (Swift)") + func nestedClassesAndStructs_swift() throws { + try assertOutput( + input: source1, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_A__00024destroy__J") + func Java_com_example_swift_A__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B__00024destroy__J") + func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B__00024destroy__J") + func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B_00024C__00024h__JJ") + func Java_com_example_swift_A_00024B_00024C__00024h__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, b: jlong, self: jlong) { + ... + } + """ + ] + ) + } + + @Test("Import: nested in enum") + func nestedEnums_java() throws { + try assertOutput( + input: """ + public enum MyError { + case text(TextMessage) + + public struct TextMessage {} + } + + public func f(text: MyError.TextMessage) {} + """, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class MyError implements JNISwiftInstance { + ... + public static final class TextMessage implements JNISwiftInstance { + ... + } + ... + public static MyError text(MyError.TextMessage arg0, SwiftArena swiftArena$) { + ... + } + """, + """ + public static void f(MyError.TextMessage text) { + ... + } + """ + ] + ) + } +} From 6419865dddef9bee0e670a1c5752342ccde0f837 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 11 Nov 2025 13:22:35 +0100 Subject: [PATCH 175/178] jextract: add support for arrays (#435) --- .../Sources/MySwiftLibrary/Arrays.swift | 60 ++++++ .../java/com/example/swift/ArraysTest.java | 105 ++++++++++ Sources/JExtractSwiftLib/CodePrinter.swift | 7 +- .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 9 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 5 +- .../JNI/JNIJavaTypeTranslator.swift | 5 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 143 +++++++++++++ ...wift2JavaGenerator+NativeTranslation.swift | 161 +++++++++++++-- .../Swift2JavaTranslator.swift | 2 + .../SwiftTypes/SwiftKnownModules.swift | 4 +- .../SwiftTypes/SwiftKnownTypeDecls.swift | 1 + .../SwiftTypes/SwiftType.swift | 17 +- .../Documentation.docc/SupportedFeatures.md | 2 +- .../JExtractSwiftTests/JNI/JNIArrayTest.swift | 188 ++++++++++++++++++ 16 files changed, 684 insertions(+), 31 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift new file mode 100644 index 00000000..b6f050c1 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +public func booleanArray(array: [Bool]) -> [Bool] { + return array +} + +public func byteArray(array: [UInt8]) -> [UInt8] { + return array +} + +public func byteArrayExplicit(array: Array) -> Array { + return array +} + +public func charArray(array: [UInt16]) -> [UInt16] { + return array +} + +public func shortArray(array: [Int16]) -> [Int16] { + return array +} + +public func intArray(array: [Int32]) -> [Int32] { + return array +} + +public func longArray(array: [Int64]) -> [Int64] { + return array +} + +public func floatArray(array: [Float]) -> [Float] { + return array +} + +public func doubleArray(array: [Double]) -> [Double] { + return array +} + +public func stringArray(array: [String]) -> [String] { + return array +} + +public func objectArray(array: [MySwiftClass]) -> [MySwiftClass] { + return array +} + diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java new file mode 100644 index 00000000..bf19b370 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class ArraysTest { + @Test + void booleanArray() { + boolean[] input = new boolean[] { true, false, false, true }; + assertArrayEquals(input, MySwiftLibrary.booleanArray(input)); + } + + @Test + void byteArray() { + byte[] input = new byte[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.byteArray(input)); + } + + @Test + void byteArray_empty() { + byte[] input = new byte[] {}; + assertArrayEquals(input, MySwiftLibrary.byteArray(input)); + } + + @Test + void byteArray_null() { + assertThrows(NullPointerException.class, () -> MySwiftLibrary.byteArray(null)); + } + + @Test + void byteArrayExplicit() { + byte[] input = new byte[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.byteArrayExplicit(input)); + } + + @Test + void charArray() { + char[] input = new char[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.charArray(input)); + } + + @Test + void shortArray() { + short[] input = new short[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.shortArray(input)); + } + + @Test + void intArray() { + int[] input = new int[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.intArray(input)); + } + + @Test + void longArray() { + long[] input = new long[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.longArray(input)); + } + + @Test + void stringArray() { + String[] input = new String[] { "hey", "there", "my", "friend" }; + assertArrayEquals(input, MySwiftLibrary.stringArray(input)); + } + + @Test + void floatArray() { + float[] input = new float[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.floatArray(input)); + } + + @Test + void doubleArray() { + double[] input = new double[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.doubleArray(input)); + } + + @Test + void objectArray() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass[] input = new MySwiftClass[]{MySwiftClass.init(arena), MySwiftClass.init(arena), MySwiftClass.init(arena) }; + assertEquals(3, MySwiftLibrary.objectArray(input, arena).length); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index e6498683..1497a61a 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -86,12 +86,17 @@ public struct CodePrinter { public mutating func printBraceBlock( _ header: Any, + parameters: [String]? = nil, function: String = #function, file: String = #fileID, line: UInt = #line, body: (inout CodePrinter) throws -> () ) rethrows { - print("\(header) {") + print("\(header) {", .continue) + if let parameters { + print(" (\(parameters.joined(separator: ", "))) in", .continue) + } + println() indent() try body(&self) outdent() diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 2621dd8b..3f821a1b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite, .array: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -127,7 +127,7 @@ extension SwiftKnownTypeDeclKind { case .void: .void case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol, - .essentialsData, .essentialsDataProtocol, .optional: + .essentialsData, .essentialsDataProtocol, .optional, .array: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 7c8d7ab2..832aff26 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -347,6 +347,9 @@ struct CdeclLowering { case .composite: throw LoweringError.unhandledType(type) + + case .array: + throw LoweringError.unhandledType(type) } } @@ -415,7 +418,7 @@ struct CdeclLowering { } throw LoweringError.unhandledType(.optional(wrappedType)) - case .function, .metatype, .optional, .composite: + case .function, .metatype, .optional, .composite, .array: throw LoweringError.unhandledType(.optional(wrappedType)) } } @@ -516,7 +519,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite, .array: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -670,7 +673,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .genericParameter, .function, .optional, .existential, .opaque, .composite: + case .genericParameter, .function, .optional, .existential, .opaque, .composite, .array: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 5d38a0f9..72da323c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -475,6 +475,9 @@ extension FFMSwift2JavaGenerator { case .composite: throw JavaTranslationError.unhandledType(swiftType) + + case .array(let elementType): + throw JavaTranslationError.unhandledType(swiftType) } } @@ -694,7 +697,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .genericParameter, .optional, .function, .existential, .opaque, .composite: + case .genericParameter, .optional, .function, .existential, .opaque, .composite, .array: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 0d1fc675..9bd9a7d8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -45,13 +45,14 @@ enum JNIJavaTypeTranslator { case .void: return .void case .string: return .javaLangString + case .int, .uint, // FIXME: why not supported int/uint? .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, - .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol: + .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array: return nil } } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 1e59c196..07d23dc0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -25,7 +25,7 @@ extension JNISwift2JavaGenerator { "java.util.concurrent.atomic.AtomicBoolean", // NonNull, Unsigned and friends - "org.swift.swiftkit.core.annotations.*", + "org.swift.swiftkit.core.annotations.*" ] } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index d9a8f8d9..9c3cdbf1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -384,6 +384,15 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -461,6 +470,12 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) + case .array(let elementType): + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -651,6 +666,14 @@ extension JNISwift2JavaGenerator { } return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateArrayResult( + elementType: elementType + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -684,6 +707,11 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) + case .array(let elementType): + return try translateArrayResult( + elementType: elementType + ) + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -771,6 +799,107 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) } } + + func translateArrayParameter( + elementType: SwiftType, + parameterName: String + ) throws -> TranslatedParameter { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + + switch elementType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .array(javaType), annotations: parameterAnnotations), + conversion: .requireNonNull(.placeholder, message: "\(parameterName) must not be null") + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + // Assume JExtract imported class + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .array(.class(package: nil, name: nominalTypeName)), + annotations: parameterAnnotations + ), + conversion: .method( + .method( + .arraysStream(.requireNonNull(.placeholder, message: "\(parameterName) must not be null")), + function: "mapToLong", + arguments: [.constant("\(nominalTypeName)::$memoryAddress")] + ), + function: "toArray", + arguments: [] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } + + func translateArrayResult( + elementType: SwiftType + ) throws -> TranslatedResult { + let annotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + + switch elementType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return TranslatedResult( + javaType: .array(javaType), + annotations: annotations, + outParameters: [], + conversion: .placeholder + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + let objectType = JavaType.class(package: nil, name: nominalTypeName) + // We assume this is a JExtract class. + return TranslatedResult( + javaType: .array(objectType), + annotations: annotations, + outParameters: [], + conversion: .method( + .method( + .arraysStream(.placeholder), + function: "mapToObj", + arguments: [ + .lambda( + args: ["pointer"], + body: .wrapMemoryAddressUnsafe(.constant("pointer"), objectType) + ) + ] + ), + function: "toArray", + arguments: [.constant("\(objectType)[]::new")] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } } struct TranslatedEnumCase { @@ -976,6 +1105,13 @@ extension JNISwift2JavaGenerator { /// Prints the conversion step, ignoring the output. indirect case print(JavaNativeConversionStep) + indirect case requireNonNull(JavaNativeConversionStep, message: String) + + /// `Arrays.stream(args)` + static func arraysStream(_ argument: JavaNativeConversionStep) -> JavaNativeConversionStep { + .method(.constant("Arrays"), function: "stream", arguments: [argument]) + } + /// 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. @@ -1105,6 +1241,10 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) printer.print("\(inner);") return "" + + case .requireNonNull(let inner, let message): + let inner = inner.render(&printer, placeholder) + return #"Objects.requireNonNull(\#(inner), "\#(message)")"# } } @@ -1161,6 +1301,9 @@ extension JNISwift2JavaGenerator { case .print(let inner): return inner.requiresSwiftArena + + case .requireNonNull(let inner, _): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 13e8c676..35935038 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -91,6 +91,12 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + return try translateArrayParameter(elementType: elementType, parameterName: parameterName) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { @@ -188,6 +194,12 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(type) + case .array(let elementType): + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } @@ -356,7 +368,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, conversion: .optionalRaisingIndirectReturn( - .getJNIValue(.allocateSwiftValue(name: "_result", swiftType: swiftType)), + .getJNIValue(.allocateSwiftValue(.placeholder, name: "_result", swiftType: swiftType)), returnType: .long, discriminatorParameterName: discriminatorName, placeholderValue: .constant("0") @@ -400,7 +412,7 @@ extension JNISwift2JavaGenerator { outParameters: [] ) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite, .array: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -429,7 +441,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite, .array: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -448,6 +460,12 @@ extension JNISwift2JavaGenerator { } return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + return try translateArrayResult(elementType: elementType, resultName: resultName) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) @@ -467,7 +485,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: resultName, swiftType: swiftResult.type)), + conversion: .getJNIValue(.allocateSwiftValue(.placeholder, name: resultName, swiftType: swiftResult.type)), outParameters: [] ) @@ -481,10 +499,108 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) + case .array(let elementType): + return try translateArrayResult(elementType: elementType, resultName: resultName) + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } + + func translateArrayResult( + elementType: SwiftType, + resultName: String + ) throws -> NativeResult { + switch elementType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + return NativeResult( + javaType: .array(javaType), + conversion: .getJNIValue(.placeholder), + outParameters: [] + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + // Assume JExtract imported class + return NativeResult( + javaType: .array(.long), + conversion: + .getJNIValue( + .method( + .placeholder, + function: "map", + arguments: [ + (nil, .closure( + args: ["object$"], + body: .allocateSwiftValue(.constant("object$"), name: "object$", swiftType: elementType) + )) + ] + ) + ), + outParameters: [] + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + } + + func translateArrayParameter( + elementType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + switch elementType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .array(javaType)), + ], + conversion: .initFromJNI(.placeholder, swiftType: .array(elementType)) + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + // Assume JExtract wrapped class + return NativeParameter( + parameters: [JavaParameter(name: parameterName, type: .array(.long))], + conversion: .method( + .initFromJNI(.placeholder, swiftType: .array(self.knownTypes.int64)), + function: "map", + arguments: [ + (nil, .closure( + args: ["pointer$"], + body: .pointee(.extractSwiftValue( + .constant("pointer$"), + swiftType: elementType, + allowNil: false, + convertLongFromJNI: false + )))) + ] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } } struct NativeFunctionSignature { @@ -539,11 +655,12 @@ extension JNISwift2JavaGenerator { indirect case extractSwiftValue( NativeSwiftConversionStep, swiftType: SwiftType, - allowNil: Bool = false + allowNil: Bool = false, + convertLongFromJNI: Bool = true ) /// Allocate memory for a Swift value and outputs the pointer - case allocateSwiftValue(name: String, swiftType: SwiftType) + indirect case allocateSwiftValue(NativeSwiftConversionStep, name: String, swiftType: SwiftType) /// The thing to which the pointer typed, which is the `pointee` property /// of the `Unsafe(Mutable)Pointer` types in Swift. @@ -575,6 +692,9 @@ extension JNISwift2JavaGenerator { isThrowing: Bool ) + /// `{ (args) -> return body }` + indirect case closure(args: [String] = [], body: NativeSwiftConversionStep) + /// 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. @@ -630,18 +750,18 @@ extension JNISwift2JavaGenerator { ) return existentialName - case .extractSwiftValue(let inner, let swiftType, let allowNil): + case .extractSwiftValue(let inner, let swiftType, let allowNil, let convertLongFromJNI): let inner = inner.render(&printer, placeholder) let pointerName = "\(inner)$" if !allowNil { printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#) } - printer.print( - """ - let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) - let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) - """ - ) + if convertLongFromJNI { + printer.print("let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment))") + } else { + printer.print("let \(inner)Bits$ = Int(\(inner))") + } + printer.print("let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$)") if !allowNil { printer.print( """ @@ -653,13 +773,14 @@ extension JNISwift2JavaGenerator { } return pointerName - case .allocateSwiftValue(let name, let swiftType): + case .allocateSwiftValue(let inner, let name, let swiftType): + let inner = inner.render(&printer, placeholder) let pointerName = "\(name)$" let bitsName = "\(name)Bits$" printer.print( """ let \(pointerName) = UnsafeMutablePointer<\(swiftType)>.allocate(capacity: 1) - \(pointerName).initialize(to: \(placeholder)) + \(pointerName).initialize(to: \(inner)) let \(bitsName) = Int64(Int(bitPattern: \(pointerName))) """ ) @@ -894,6 +1015,16 @@ extension JNISwift2JavaGenerator { } return "" + + case .closure(let args, let body): + var printer = CodePrinter() + printer.printBraceBlock("", parameters: args) { printer in + let body = body.render(&printer, placeholder) + if !body.isEmpty { + printer.print("return \(body)") + } + } + return printer.finalize() } } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 7de792c0..60b99a63 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -142,6 +142,8 @@ extension Swift2JavaTranslator { return types.contains(where: check) case .genericParameter: return false + case .array(let ty): + return check(ty) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 7acb1199..1be8071c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -94,6 +94,8 @@ private let swiftSourceFile: SourceFileSyntax = """ public struct Optional {} + public struct Array {} + // FIXME: Support 'typealias Void = ()' public struct Void {} @@ -117,4 +119,4 @@ private var foundationSourceFile: SourceFileSyntax { // On platforms other than Darwin, imports such as FoundationEssentials, FoundationNetworking, etc. are used, // so this file should be created by combining the files of the aforementioned modules. foundationEssentialsSourceFile -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 809f2aa1..4a0cb9e8 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -40,6 +40,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case optional = "Swift.Optional" case void = "Swift.Void" case string = "Swift.String" + case array = "Swift.Array" // Foundation case foundationDataProtocol = "Foundation.DataProtocol" diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 3840b3e1..b2f8d6ea 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -40,6 +40,9 @@ enum SwiftType: Equatable { /// `type1` & `type2` indirect case composite([SwiftType]) + /// `[type]` + indirect case array(SwiftType) + static var void: Self { return .tuple([]) } @@ -48,7 +51,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite, .array: nil } } @@ -91,7 +94,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .genericParameter, .optional, .tuple, .existential, .opaque, .composite: + case .genericParameter, .optional, .tuple, .existential, .opaque, .composite, .array: return false } } @@ -127,7 +130,7 @@ extension SwiftType: CustomStringConvertible { private var postfixRequiresParentheses: Bool { switch self { case .function, .existential, .opaque, .composite: true - case .genericParameter, .metatype, .nominal, .optional, .tuple: false + case .genericParameter, .metatype, .nominal, .optional, .tuple, .array: false } } @@ -152,6 +155,8 @@ extension SwiftType: CustomStringConvertible { return "some \(constraintType)" case .composite(let types): return types.map(\.description).joined(separator: " & ") + case .array(let type): + return "[\(type)]" } } } @@ -213,7 +218,7 @@ extension SwiftNominalType { extension SwiftType { init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { - case .arrayType, .classRestrictionType, + case .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, .packElementType, .packExpansionType, .suppressedType, .inlineArrayType: throw TypeTranslationError.unimplementedType(type) @@ -323,6 +328,10 @@ extension SwiftType { } self = .composite(types) + + case .arrayType(let arrayType): + let elementType = try SwiftType(arrayType.element, lookupContext: lookupContext) + self = .array(elementType) } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 3178f60d..7e8c66d3 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -58,7 +58,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | | Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | | Async functions `func async` and properties: `var { get async {} }` | ❌ | ✅ | -| Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | +| Arrays: `[UInt8]`, `[MyType]`, `Array` etc | ❌ | ✅ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | | Generic return values in functions: `func f() -> T` | ❌ | ❌ | diff --git a/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift new file mode 100644 index 00000000..ebe3f807 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// 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 JNIArrayTest { + + @Test("Import: (Array) -> Array (Java)") + func uint8Array_explicitType_java() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static byte[] f(@Unsigned byte[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native byte[] $f(byte[] array); + """, + ] + ) + } + + @Test("Import: (Array) -> Array (Swift)") + func uint8Array_explicitType_swift() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3B") + func Java_com_example_swift_SwiftModule__00024f___3B(environment: UnsafeMutablePointer!, thisClass: jclass, array: jbyteArray?) -> jbyteArray? { + return SwiftModule.f(array: [UInt8](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([UInt8]) -> [UInt8] (Java)") + func uint8Array_syntaxSugar_java() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static byte[] f(@Unsigned byte[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native byte[] $f(byte[] array); + """, + ] + ) + } + + @Test("Import: ([UInt8]) -> [UInt8] (Swift)") + func uint8Array_syntaxSugar_swift() throws { + try assertOutput( + input: "public func f(array: [UInt8]) -> [UInt8] {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3B") + func Java_com_example_swift_SwiftModule__00024f___3B(environment: UnsafeMutablePointer!, thisClass: jclass, array: jbyteArray?) -> jbyteArray? { + return SwiftModule.f(array: [UInt8](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([Int64]) -> [Int64] (Java)") + func int64Array_syntaxSugar_java() throws { + try assertOutput( + input: "public func f(array: [Int64]) -> [Int64] {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static long[] f(long[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native long[] $f(long[] array); + """, + ] + ) + } + + @Test("Import: ([Int64]) -> [Int64] (Swift)") + func int64Array_syntaxSugar_swift() throws { + try assertOutput( + input: "public func f(array: [Int64]) -> [Int64] {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3J") + func Java_com_example_swift_SwiftModule__00024f___3J(environment: UnsafeMutablePointer!, thisClass: jclass, array: jlongArray?) -> jlongArray? { + return SwiftModule.f(array: [Int64](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([MySwiftClass]) -> [MySwiftClass] (Java)") + func swiftClassArray_syntaxSugar_java() throws { + try assertOutput( + input: """ + public class MySwiftClass {} + public func f(array: [MySwiftClass]) -> [MySwiftClass] {} + """, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static MySwiftClass[] f(MySwiftClass[] array, SwiftArena swiftArena$) { + return Arrays.stream(SwiftModule.$f(Arrays.stream(Objects.requireNonNull(array, "array must not be null")).mapToLong(MySwiftClass::$memoryAddress).toArray())).mapToObj((pointer) -> { + return MySwiftClass.wrapMemoryAddressUnsafe(pointer, swiftArena$); + } + ).toArray(MySwiftClass[]::new); + } + """, + """ + private static native long[] $f(long[] array); + """, + ] + ) + } + + @Test("Import: ([MySwiftClass]) -> [MySwiftClass] (Swift)") + func swiftClassArray_syntaxSugar_swift() throws { + try assertOutput( + input: """ + public class MySwiftClass {} + public func f(array: [MySwiftClass]) -> [MySwiftClass] {} + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3J") + func Java_com_example_swift_SwiftModule__00024f___3J(environment: UnsafeMutablePointer!, thisClass: jclass, array: jlongArray?) -> jlongArray? { + return SwiftModule.f(array: [Int64](fromJNI: array, in: environment).map( { (pointer$) in + assert(pointer$ != 0, "pointer$ memory address was null") + let pointer$Bits$ = Int(pointer$) + let pointer$$ = UnsafeMutablePointer(bitPattern: pointer$Bits$) + guard let pointer$$ else { + fatalError("pointer$ memory address was null in call to \\(#function)!") + } + return pointer$$.pointee + } + )).map( { (object$) in + let object$$ = UnsafeMutablePointer.allocate(capacity: 1) + object$$.initialize(to: object$) + let object$Bits$ = Int64(Int(bitPattern: object$$)) + return object$Bits$ + } + ).getJNIValue(in: environment) + } + """ + ] + ) + } +} From e907ab050e4cac6d22bd6c72a1449b3a1f55a106 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 12 Nov 2025 12:56:36 +0900 Subject: [PATCH 176/178] Delete WIP.md (#438) --- WIP.md | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 WIP.md diff --git a/WIP.md b/WIP.md deleted file mode 100644 index 88671233..00000000 --- a/WIP.md +++ /dev/null @@ -1,25 +0,0 @@ -## Work In Progress - -This package is a work in progress, and many details are subject to change. - -Here is a long yet still very incomplete list of things we would like to do or -improve: - -- Expressivity gaps: - - [ ] Automatically turn get/set method pairs into Swift properties? - - [ ] Implement a global registry that lets us find the Swift type corresponding to a canonical Java class name (e.g., `java.net.URL` -> `JavaKitNetwork.URL`) - - [ ] Introduce overloads of `is` and `as` on the Swift projections so that conversion to any implemented interface or extended superclass returns non-optional. - - [ ] Figure out how to express the equivalent of `super.foo()` that calls the superclass's method from the subclass method. - - [ ] Recognize Java's enum classes and map them into Swift well - - [ ] Translate Java constants into Swift constants - - [ ] Support nested classes - - [ ] Figure out how to subclass a Java class from Swift -- Tooling - - [ ] Generate Swift projections for more common Java types into JavaKit libraries to make it easier to get started - - [ ] Teach `Java2Swift` when to create extensions of already-translated types that pick up any members that couldn't be translated because of missing types. See, for example, how `JavaKitReflection` adds extensions to `JavaClass` based on types like `Method` and `Parameter` -- Performance: - - [ ] Cache method/field IDs when we can - - [ ] Investigate noncopyable types to remove excess copies - - [ ] Investigate "unbridged" variants of String, Array, etc. - - [ ] Investigate the new [Foreign Function & Memory API](https://bugs.openjdk.org/browse/JDK-8312523) (aka Project Panama) for exposing Swift APIs to Java. - From 46699587ec25374ec6f0431beb6ea29dfa8d9758 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 12 Nov 2025 14:17:09 +0900 Subject: [PATCH 177/178] jextract/jni: use deferEnvironment instead of environment interface (#437) --- ...wift2JavaGenerator+NativeTranslation.swift | 2 +- .../JNI/JNIAsyncTests.swift | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 35935038..1994dce0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -973,7 +973,7 @@ extension JNISwift2JavaGenerator { // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") for globalRef in globalRefs { - printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") + printer.print("deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") } } if isThrowing { diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 975cccc6..1930e601 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -66,7 +66,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() environment = try! JavaVirtualMachine.shared().environment() @@ -79,7 +79,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() environment = try! JavaVirtualMachine.shared().environment() @@ -140,7 +140,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } do { let swiftResult$ = await try SwiftModule.async() @@ -160,7 +160,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } do { let swiftResult$ = await try SwiftModule.async() @@ -228,7 +228,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try! JavaVirtualMachine.shared().environment() @@ -242,7 +242,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try! JavaVirtualMachine.shared().environment() @@ -319,7 +319,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) environment = try! JavaVirtualMachine.shared().environment() @@ -336,7 +336,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) environment = try! JavaVirtualMachine.shared().environment() @@ -397,8 +397,8 @@ struct JNIAsyncTests { ... defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) - environment.interface.DeleteGlobalRef(deferEnvironment, s) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, s) } ... environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: swiftResult$.getJNIValue(in: environment))]) From f010c8e33d528085057bb8e82eb53136df30effd Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 12 Nov 2025 19:32:42 +0900 Subject: [PATCH 178/178] wrap-java revamp and add generic methods, types, and supertypes support (#397) --- Package.swift | 4 +- Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 31 ++ .../Sources/JavaCommonsCSV/swift-java.config | 4 +- .../JavaDependencySampleApp/ci-validate.sh | 4 +- .../Sources/MySwiftLibrary/NestedTypes.swift | 2 +- .../com/example/swift/HelloJava2SwiftJNI.java | 2 - .../JavaLangReflect/Method+Utilities.swift | 26 +- .../TypeVariable+Extensions.swift | 63 ++++ .../generated/Executable.swift | 3 - .../generated/ParameterizedType.swift | 21 ++ .../JavaLangReflect/generated/Type.swift | 9 + .../generated/TypeVariable.swift | 4 +- .../JavaStdlib/JavaNet/URL+Extensions.swift | 34 ++ .../JavaNet/URLClassLoader+Workaround.swift | 22 ++ Sources/SwiftJava/AnyJavaObject.swift | 6 +- .../JavaVirtualMachine.swift | 2 +- .../{JavaKitVM => JVM}/LockedState.swift | 0 .../ThreadLocalStorage.swift | 0 .../JavaClass+CustomStringConvertible.swift | 21 ++ Sources/SwiftJava/JavaObjectHolder.swift | 4 +- .../String+Extensions.swift | 18 +- .../SwiftJava/SwiftJavaConversionError.swift | 22 ++ .../SwiftJava/generated/CharSequence.swift | 3 + .../SwiftJava/generated/JavaCharacter.swift | 328 +++++++++--------- Sources/SwiftJava/generated/JavaClass.swift | 34 +- .../SwiftJava/generated/JavaClassLoader.swift | 8 +- Sources/SwiftJava/generated/JavaEnum.swift | 11 + Sources/SwiftJava/generated/JavaInteger.swift | 29 +- Sources/SwiftJava/generated/JavaLong.swift | 18 +- .../SwiftJava/generated/JavaOptional.swift | 2 +- .../generated/JavaReflectArray.swift | 71 ++++ .../JavaReflectParameterizedType.swift | 17 + .../SwiftJava/generated/JavaReflectType.swift | 8 + Sources/SwiftJava/swift-java.config | 2 + .../Configuration.swift | 8 +- .../Commands/ConfigureCommand.swift | 56 ++- .../Commands/JExtractCommand.swift | 10 +- .../Commands/ResolveCommand.swift | 22 +- .../Commands/WrapJavaCommand.swift | 52 ++- Sources/SwiftJavaTool/CommonOptions.swift | 21 +- Sources/SwiftJavaTool/ExcludedJDKTypes.swift | 42 +++ .../SwiftJavaTool/Java/JavaClassLoader.swift | 1 + .../JavaClassTranslator.swift | 309 +++++++++-------- .../JavaGenericsSupport.swift | 89 +++++ .../SwiftJavaToolLib/JavaHomeSupport.swift | 92 +++++ .../JavaParameterizedType.swift | 38 ++ .../JavaTranslator+Configuration.swift | 10 +- .../JavaTranslator+Validation.swift | 53 ++- Sources/SwiftJavaToolLib/JavaTranslator.swift | 114 ++++-- .../SwiftJavaToolLib/JavaType+Equality.swift | 135 +++++++ .../FFMNestedTypesTests.swift | 56 +++ .../JNI/JNINestedTypesTests.swift | 2 +- .../JNI/JNIUnsignedNumberTests.swift | 1 - .../CompileJavaWrapTools.swift | 174 ++++++++++ .../Java2SwiftTests.swift | 278 ++++++++------- .../JavaTranslatorTests.swift | 58 ++++ .../JavaTranslatorValidationTests.swift | 13 +- .../SwiftJavaToolLibTests/TempFileTools.swift | 34 ++ .../SwiftJavaToolLibTests/WrapJavaTests.swift | 273 +++++++++++++++ 59 files changed, 2150 insertions(+), 624 deletions(-) create mode 100644 Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift create mode 100644 Sources/JavaStdlib/JavaNet/URL+Extensions.swift create mode 100644 Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift rename Sources/SwiftJava/{JavaKitVM => JVM}/JavaVirtualMachine.swift (99%) rename Sources/SwiftJava/{JavaKitVM => JVM}/LockedState.swift (100%) rename Sources/SwiftJava/{JavaKitVM => JVM}/ThreadLocalStorage.swift (100%) create mode 100644 Sources/SwiftJava/JavaClass+CustomStringConvertible.swift rename Sources/{SwiftJavaTool => SwiftJava}/String+Extensions.swift (82%) create mode 100644 Sources/SwiftJava/SwiftJavaConversionError.swift create mode 100644 Sources/SwiftJava/generated/JavaEnum.swift create mode 100644 Sources/SwiftJava/generated/JavaReflectArray.swift create mode 100644 Sources/SwiftJava/generated/JavaReflectParameterizedType.swift create mode 100644 Sources/SwiftJava/generated/JavaReflectType.swift create mode 100644 Sources/SwiftJavaTool/ExcludedJDKTypes.swift create mode 100644 Sources/SwiftJavaToolLib/JavaGenericsSupport.swift create mode 100644 Sources/SwiftJavaToolLib/JavaHomeSupport.swift create mode 100644 Sources/SwiftJavaToolLib/JavaParameterizedType.swift create mode 100644 Sources/SwiftJavaToolLib/JavaType+Equality.swift create mode 100644 Tests/JExtractSwiftTests/FFMNestedTypesTests.swift create mode 100644 Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift create mode 100644 Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift create mode 100644 Tests/SwiftJavaToolLibTests/TempFileTools.swift create mode 100644 Tests/SwiftJavaToolLibTests/WrapJavaTests.swift diff --git a/Package.swift b/Package.swift index 722859ee..7d872795 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,9 @@ func findJavaHome() -> String { print("JAVA_HOME = \(home)") return home } + if let opts = ProcessInfo.processInfo.environment["SWIFT_JAVA_JAVA_OPTS"] { + print("SWIFT_JAVA_JAVA_OPTS = \(opts)") + } // This is a workaround for envs (some IDEs) which have trouble with // picking up env variables during the build process @@ -420,7 +423,6 @@ let package = Package( .executableTarget( name: "SwiftJavaTool", dependencies: [ - .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 57641eff..7908932d 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -165,6 +165,11 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("No dependencies to fetch for target \(sourceModule.name)") } + // Add all the core Java stdlib modules as --depends-on + let javaStdlibModules = getExtractedJavaStdlibModules() + log("Include Java standard library SwiftJava modules: \(javaStdlibModules)") + arguments += javaStdlibModules.flatMap { ["--depends-on", $0] } + if !outputSwiftFiles.isEmpty { arguments += [ configFile.path(percentEncoded: false) ] @@ -236,3 +241,29 @@ extension SwiftJavaBuildToolPlugin { outputDirectory(context: context, generated: generated).appending(path: filename) } } + +func getExtractedJavaStdlibModules() -> [String] { + let fileManager = FileManager.default + let sourcesPath = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent("Sources") + .appendingPathComponent("JavaStdlib") + + guard let stdlibDirContents = try? fileManager.contentsOfDirectory( + at: sourcesPath, + includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles] + ) else { + return [] + } + + return stdlibDirContents.compactMap { url in + guard let resourceValues = try? url.resourceValues(forKeys: [.isDirectoryKey]), + let isDirectory = resourceValues.isDirectory, + isDirectory else { + return nil + } + return url.lastPathComponent + }.sorted() +} \ No newline at end of file diff --git a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config index 3b685159..dc38efd9 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config +++ b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config @@ -1,11 +1,13 @@ { "classes" : { "org.apache.commons.io.FilenameUtils" : "FilenameUtils", - "org.apache.commons.io.IOCase" : "IOCase", "org.apache.commons.csv.CSVFormat" : "CSVFormat", "org.apache.commons.csv.CSVParser" : "CSVParser", "org.apache.commons.csv.CSVRecord" : "CSVRecord" }, + "filterExclude" : [ + "org.apache.commons.csv.CSVFormat$Predefined", + ], "dependencies" : [ "org.apache.commons:commons-csv:1.12.0" ] diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index b6162fdc..feeb8767 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -4,7 +4,9 @@ set -e set -x # invoke resolve as part of a build run -swift run --disable-sandbox +swift build \ + --disable-experimental-prebuilts \ + --disable-sandbox # explicitly invoke resolve without explicit path or dependency # the dependencies should be uses from the --swift-module diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift index b7edefa9..fb2b4924 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift @@ -44,4 +44,4 @@ public enum NestedEnum { public struct OneStruct { public init() {} } -} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 3109f64e..407ebe7c 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -16,8 +16,6 @@ // Import swift-extract generated sources -// Import javakit/swiftkit support libraries - import org.swift.swiftkit.core.SwiftArena; import org.swift.swiftkit.core.SwiftLibraries; diff --git a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift index 71ee864c..ecc11b50 100644 --- a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift @@ -13,23 +13,41 @@ //===----------------------------------------------------------------------===// extension Method { + /// Whether this is a 'public' method. public var isPublic: Bool { - return (getModifiers() & 1) != 0 + return (getModifiers() & 0x00000001) != 0 + } + + /// Whether this is a 'private' method. + public var isPrivate: Bool { + return (getModifiers() & 0x00000002) != 0 } /// Whether this is a 'protected' method. public var isProtected: Bool { - return (getModifiers() & 4) != 0 + return (getModifiers() & 0x00000004) != 0 + } + + /// Whether this is a 'package' method. + /// + /// The "default" access level in Java is 'package', it is signified by lack of a different access modifier. + public var isPackage: Bool { + return !isPublic && !isPrivate && !isProtected } /// Whether this is a 'static' method. public var isStatic: Bool { - return (getModifiers() & 0x08) != 0 + return (getModifiers() & 0x00000008) != 0 } /// Whether this is a 'native' method. public var isNative: Bool { - return (getModifiers() & 256) != 0 + return (getModifiers() & 0x00000100) != 0 + } + + /// Whether this is a 'final' method. + public var isFinal: Bool { + return (getModifiers() & 0x00000010) != 0 } } diff --git a/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift new file mode 100644 index 00000000..157ae353 --- /dev/null +++ b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 CSwiftJavaJNI +import SwiftJava + +// FIXME: all interfaces should ahve these https://github.com/swiftlang/swift-java/issues/430 +extension TypeVariable { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 + +} + +// FIXME: All Java objects are Hashable, we should handle that accordingly. +extension TypeVariable: Hashable { + + public func hash(into hasher: inout Hasher) { + guard let pojo = self.as(JavaObject.self) else { + return + } + + hasher.combine(pojo.hashCode()) + } + + public static func == (lhs: TypeVariable, rhs: TypeVariable) -> Bool { + guard let lhpojo: JavaObject = lhs.as(JavaObject.self) else { + return false + } + guard let rhpojo: JavaObject = rhs.as(JavaObject.self) else { + return false + } + + return lhpojo.equals(rhpojo) + } + +} + +extension TypeVariable { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 3a6df8ea..af9931d3 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -11,9 +11,6 @@ open class Executable: AccessibleObject { @JavaMethod open func getModifiers() -> Int32 - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] - @JavaMethod open func getParameterTypes() -> [JavaClass?] diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index 5e29ee05..984c2b16 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -16,3 +16,24 @@ public struct ParameterizedType { @JavaMethod public func getTypeName() -> String } + +extension ParameterizedType { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 +} + +extension ParameterizedType: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index ff52b41a..2e85c384 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -6,4 +6,13 @@ import CSwiftJavaJNI public struct Type { @JavaMethod public func getTypeName() -> String + + @JavaMethod + public func toString() -> String +} + +extension Type: CustomStringConvertible { + public var description: String { + "JavaLangReflect.Type(\(self.toString()))" + } } diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index 736fcfde..7bf8f7ba 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -3,10 +3,10 @@ import SwiftJava import CSwiftJavaJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) -public struct TypeVariable { +public struct TypeVariable: CustomStringConvertible { @JavaMethod public func getGenericDeclaration() -> GenericDeclaration! - + @JavaMethod public func getAnnotatedBounds() -> [AnnotatedType?] diff --git a/Sources/JavaStdlib/JavaNet/URL+Extensions.swift b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift new file mode 100644 index 00000000..2b220d1c --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava +import CSwiftJavaJNI + +import Foundation +public typealias SwiftJavaFoundationURL = Foundation.URL + +extension SwiftJavaFoundationURL { + public static func fromJava(_ url: URL) throws -> SwiftJavaFoundationURL { + guard let converted = SwiftJavaFoundationURL(string: try url.toURI().toString()) else { + throw SwiftJavaConversionError("Failed to convert \(URL.self) to \(SwiftJavaFoundationURL.self)") + } + return converted + } +} + +extension URL { + public static func fromSwift(_ url: SwiftJavaFoundationURL) throws -> URL { + return try URL(url.absoluteString) + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift new file mode 100644 index 00000000..4c55f6af --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 CSwiftJavaJNI +import SwiftJava + +// FIXME: workaround until importing properly would make UCL inherit from CL https://github.com/swiftlang/swift-java/issues/423 +extension URLClassLoader /* workaround for missing inherits from ClassLoader */ { + @JavaMethod + public func loadClass(_ name: String) throws -> JavaClass? +} diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index e514d3e6..bf749820 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -52,7 +52,7 @@ public protocol AnyJavaObject { /// Protocol that allows Swift types to specify a custom Java class loader on /// initialization. This is useful for platforms (e.g. Android) where the default /// class loader does not make all application classes visible. -public protocol CustomJavaClassLoader: AnyJavaObject { +public protocol AnyJavaObjectWithCustomClassLoader: AnyJavaObject { static func getJavaClassLoader(in environment: JNIEnvironment) throws -> JavaClassLoader! } @@ -118,8 +118,8 @@ extension AnyJavaObject { in environment: JNIEnvironment, _ body: (jclass) throws -> Result ) throws -> Result { - if let customJavaClassLoader = self as? CustomJavaClassLoader.Type, - let customClassLoader = try customJavaClassLoader.getJavaClassLoader(in: environment) { + if let AnyJavaObjectWithCustomClassLoader = self as? AnyJavaObjectWithCustomClassLoader.Type, + let customClassLoader = try AnyJavaObjectWithCustomClassLoader.getJavaClassLoader(in: environment) { try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body) } else { try _withJNIClassFromDefaultClassLoader(in: environment, body) diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift similarity index 99% rename from Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift rename to Sources/SwiftJava/JVM/JavaVirtualMachine.swift index bb574c8a..1c7936a3 100644 --- a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -350,4 +350,4 @@ extension JavaVirtualMachine { enum JavaKitError: Error { case classpathEntryNotFound(entry: String, classpath: [String]) } -} +} \ No newline at end of file diff --git a/Sources/SwiftJava/JavaKitVM/LockedState.swift b/Sources/SwiftJava/JVM/LockedState.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/LockedState.swift rename to Sources/SwiftJava/JVM/LockedState.swift diff --git a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JVM/ThreadLocalStorage.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift rename to Sources/SwiftJava/JVM/ThreadLocalStorage.swift diff --git a/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift new file mode 100644 index 00000000..e7eb2510 --- /dev/null +++ b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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 CSwiftJavaJNI + +extension JavaClass: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 319a09e8..5930da59 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -17,8 +17,8 @@ import CSwiftJavaJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object /// while this instance is live. -public class JavaObjectHolder { - public private(set) var object: jobject? +public final class JavaObjectHolder { + public private(set) var object: jobject? // TODO: thread-safety public let environment: JNIEnvironment /// Take a reference to a Java object and promote it to a global reference diff --git a/Sources/SwiftJavaTool/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift similarity index 82% rename from Sources/SwiftJavaTool/String+Extensions.swift rename to Sources/SwiftJava/String+Extensions.swift index 304e217d..94fb1928 100644 --- a/Sources/SwiftJavaTool/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -13,17 +13,15 @@ //===----------------------------------------------------------------------===// import Foundation -import ArgumentParser -import SwiftJavaToolLib -import SwiftJava -import JavaUtilJar -import SwiftJavaToolLib -import SwiftJavaConfigurationShared +// import SwiftJavaToolLib +// import SwiftJava +// import JavaUtilJar +// import SwiftJavaConfigurationShared extension String { /// For a String that's of the form java.util.Vector, return the "Vector" /// part. - var defaultSwiftNameForJavaClass: String { + package var defaultSwiftNameForJavaClass: String { if let dotLoc = lastIndex(of: ".") { let afterDot = index(after: dotLoc) return String(self[afterDot...]).javaClassNameToCanonicalName.adjustedSwiftTypeName @@ -36,12 +34,12 @@ extension String { extension String { /// Replace all of the $'s for nested names with "." to turn a Java class /// name into a Java canonical class name, - var javaClassNameToCanonicalName: String { + package var javaClassNameToCanonicalName: String { return replacing("$", with: ".") } /// Whether this is the name of an anonymous class. - var isLocalJavaClass: Bool { + package var isLocalJavaClass: Bool { for segment in split(separator: "$") { if let firstChar = segment.first, firstChar.isNumber { return true @@ -52,7 +50,7 @@ extension String { } /// Adjust type name for "bad" type names that don't work well in Swift. - var adjustedSwiftTypeName: String { + package var adjustedSwiftTypeName: String { switch self { case "Type": return "JavaType" default: return self diff --git a/Sources/SwiftJava/SwiftJavaConversionError.swift b/Sources/SwiftJava/SwiftJavaConversionError.swift new file mode 100644 index 00000000..5b29741c --- /dev/null +++ b/Sources/SwiftJava/SwiftJavaConversionError.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Used to indicate Swift/Java conversion failures. +public struct SwiftJavaConversionError: Error { + public let message: String + + public init(_ message: String) { + self.message = message + } +} \ No newline at end of file diff --git a/Sources/SwiftJava/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift index eadc509e..ee5dca36 100644 --- a/Sources/SwiftJava/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -9,6 +9,9 @@ public struct CharSequence { @JavaMethod public func toString() -> String + @JavaMethod + public func getChars(_ arg0: Int32, _ arg1: Int32, _ arg2: [UInt16], _ arg3: Int32) + @JavaMethod public func charAt(_ arg0: Int32) -> UInt16 diff --git a/Sources/SwiftJava/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift index 406b45ee..f79742a3 100644 --- a/Sources/SwiftJava/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1557,985 +1557,985 @@ extension JavaCharacter { if let COMMON = classObj.COMMON { self.init(javaHolder: COMMON.javaHolder) } else { - fatalError("Enum value COMMON was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COMMON was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LATIN: if let LATIN = classObj.LATIN { self.init(javaHolder: LATIN.javaHolder) } else { - fatalError("Enum value LATIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LATIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GREEK: if let GREEK = classObj.GREEK { self.init(javaHolder: GREEK.javaHolder) } else { - fatalError("Enum value GREEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GREEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYRILLIC: if let CYRILLIC = classObj.CYRILLIC { self.init(javaHolder: CYRILLIC.javaHolder) } else { - fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARMENIAN: if let ARMENIAN = classObj.ARMENIAN { self.init(javaHolder: ARMENIAN.javaHolder) } else { - fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HEBREW: if let HEBREW = classObj.HEBREW { self.init(javaHolder: HEBREW.javaHolder) } else { - fatalError("Enum value HEBREW was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HEBREW was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARABIC: if let ARABIC = classObj.ARABIC { self.init(javaHolder: ARABIC.javaHolder) } else { - fatalError("Enum value ARABIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARABIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYRIAC: if let SYRIAC = classObj.SYRIAC { self.init(javaHolder: SYRIAC.javaHolder) } else { - fatalError("Enum value SYRIAC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYRIAC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAANA: if let THAANA = classObj.THAANA { self.init(javaHolder: THAANA.javaHolder) } else { - fatalError("Enum value THAANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DEVANAGARI: if let DEVANAGARI = classObj.DEVANAGARI { self.init(javaHolder: DEVANAGARI.javaHolder) } else { - fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BENGALI: if let BENGALI = classObj.BENGALI { self.init(javaHolder: BENGALI.javaHolder) } else { - fatalError("Enum value BENGALI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BENGALI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GURMUKHI: if let GURMUKHI = classObj.GURMUKHI { self.init(javaHolder: GURMUKHI.javaHolder) } else { - fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUJARATI: if let GUJARATI = classObj.GUJARATI { self.init(javaHolder: GUJARATI.javaHolder) } else { - fatalError("Enum value GUJARATI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUJARATI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ORIYA: if let ORIYA = classObj.ORIYA { self.init(javaHolder: ORIYA.javaHolder) } else { - fatalError("Enum value ORIYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ORIYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAMIL: if let TAMIL = classObj.TAMIL { self.init(javaHolder: TAMIL.javaHolder) } else { - fatalError("Enum value TAMIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAMIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TELUGU: if let TELUGU = classObj.TELUGU { self.init(javaHolder: TELUGU.javaHolder) } else { - fatalError("Enum value TELUGU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TELUGU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KANNADA: if let KANNADA = classObj.KANNADA { self.init(javaHolder: KANNADA.javaHolder) } else { - fatalError("Enum value KANNADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KANNADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MALAYALAM: if let MALAYALAM = classObj.MALAYALAM { self.init(javaHolder: MALAYALAM.javaHolder) } else { - fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SINHALA: if let SINHALA = classObj.SINHALA { self.init(javaHolder: SINHALA.javaHolder) } else { - fatalError("Enum value SINHALA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SINHALA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAI: if let THAI = classObj.THAI { self.init(javaHolder: THAI.javaHolder) } else { - fatalError("Enum value THAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LAO: if let LAO = classObj.LAO { self.init(javaHolder: LAO.javaHolder) } else { - fatalError("Enum value LAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIBETAN: if let TIBETAN = classObj.TIBETAN { self.init(javaHolder: TIBETAN.javaHolder) } else { - fatalError("Enum value TIBETAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIBETAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MYANMAR: if let MYANMAR = classObj.MYANMAR { self.init(javaHolder: MYANMAR.javaHolder) } else { - fatalError("Enum value MYANMAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MYANMAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GEORGIAN: if let GEORGIAN = classObj.GEORGIAN { self.init(javaHolder: GEORGIAN.javaHolder) } else { - fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANGUL: if let HANGUL = classObj.HANGUL { self.init(javaHolder: HANGUL.javaHolder) } else { - fatalError("Enum value HANGUL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANGUL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ETHIOPIC: if let ETHIOPIC = classObj.ETHIOPIC { self.init(javaHolder: ETHIOPIC.javaHolder) } else { - fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHEROKEE: if let CHEROKEE = classObj.CHEROKEE { self.init(javaHolder: CHEROKEE.javaHolder) } else { - fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CANADIAN_ABORIGINAL: if let CANADIAN_ABORIGINAL = classObj.CANADIAN_ABORIGINAL { self.init(javaHolder: CANADIAN_ABORIGINAL.javaHolder) } else { - fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OGHAM: if let OGHAM = classObj.OGHAM { self.init(javaHolder: OGHAM.javaHolder) } else { - fatalError("Enum value OGHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OGHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .RUNIC: if let RUNIC = classObj.RUNIC { self.init(javaHolder: RUNIC.javaHolder) } else { - fatalError("Enum value RUNIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value RUNIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHMER: if let KHMER = classObj.KHMER { self.init(javaHolder: KHMER.javaHolder) } else { - fatalError("Enum value KHMER was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHMER was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MONGOLIAN: if let MONGOLIAN = classObj.MONGOLIAN { self.init(javaHolder: MONGOLIAN.javaHolder) } else { - fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HIRAGANA: if let HIRAGANA = classObj.HIRAGANA { self.init(javaHolder: HIRAGANA.javaHolder) } else { - fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KATAKANA: if let KATAKANA = classObj.KATAKANA { self.init(javaHolder: KATAKANA.javaHolder) } else { - fatalError("Enum value KATAKANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KATAKANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BOPOMOFO: if let BOPOMOFO = classObj.BOPOMOFO { self.init(javaHolder: BOPOMOFO.javaHolder) } else { - fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HAN: if let HAN = classObj.HAN { self.init(javaHolder: HAN.javaHolder) } else { - fatalError("Enum value HAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YI: if let YI = classObj.YI { self.init(javaHolder: YI.javaHolder) } else { - fatalError("Enum value YI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_ITALIC: if let OLD_ITALIC = classObj.OLD_ITALIC { self.init(javaHolder: OLD_ITALIC.javaHolder) } else { - fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GOTHIC: if let GOTHIC = classObj.GOTHIC { self.init(javaHolder: GOTHIC.javaHolder) } else { - fatalError("Enum value GOTHIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GOTHIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DESERET: if let DESERET = classObj.DESERET { self.init(javaHolder: DESERET.javaHolder) } else { - fatalError("Enum value DESERET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DESERET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INHERITED: if let INHERITED = classObj.INHERITED { self.init(javaHolder: INHERITED.javaHolder) } else { - fatalError("Enum value INHERITED was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INHERITED was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGALOG: if let TAGALOG = classObj.TAGALOG { self.init(javaHolder: TAGALOG.javaHolder) } else { - fatalError("Enum value TAGALOG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGALOG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANUNOO: if let HANUNOO = classObj.HANUNOO { self.init(javaHolder: HANUNOO.javaHolder) } else { - fatalError("Enum value HANUNOO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANUNOO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUHID: if let BUHID = classObj.BUHID { self.init(javaHolder: BUHID.javaHolder) } else { - fatalError("Enum value BUHID was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUHID was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGBANWA: if let TAGBANWA = classObj.TAGBANWA { self.init(javaHolder: TAGBANWA.javaHolder) } else { - fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LIMBU: if let LIMBU = classObj.LIMBU { self.init(javaHolder: LIMBU.javaHolder) } else { - fatalError("Enum value LIMBU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LIMBU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_LE: if let TAI_LE = classObj.TAI_LE { self.init(javaHolder: TAI_LE.javaHolder) } else { - fatalError("Enum value TAI_LE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_LE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_B: if let LINEAR_B = classObj.LINEAR_B { self.init(javaHolder: LINEAR_B.javaHolder) } else { - fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UGARITIC: if let UGARITIC = classObj.UGARITIC { self.init(javaHolder: UGARITIC.javaHolder) } else { - fatalError("Enum value UGARITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UGARITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHAVIAN: if let SHAVIAN = classObj.SHAVIAN { self.init(javaHolder: SHAVIAN.javaHolder) } else { - fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSMANYA: if let OSMANYA = classObj.OSMANYA { self.init(javaHolder: OSMANYA.javaHolder) } else { - fatalError("Enum value OSMANYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSMANYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRIOT: if let CYPRIOT = classObj.CYPRIOT { self.init(javaHolder: CYPRIOT.javaHolder) } else { - fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAILLE: if let BRAILLE = classObj.BRAILLE { self.init(javaHolder: BRAILLE.javaHolder) } else { - fatalError("Enum value BRAILLE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAILLE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUGINESE: if let BUGINESE = classObj.BUGINESE { self.init(javaHolder: BUGINESE.javaHolder) } else { - fatalError("Enum value BUGINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUGINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .COPTIC: if let COPTIC = classObj.COPTIC { self.init(javaHolder: COPTIC.javaHolder) } else { - fatalError("Enum value COPTIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COPTIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEW_TAI_LUE: if let NEW_TAI_LUE = classObj.NEW_TAI_LUE { self.init(javaHolder: NEW_TAI_LUE.javaHolder) } else { - fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GLAGOLITIC: if let GLAGOLITIC = classObj.GLAGOLITIC { self.init(javaHolder: GLAGOLITIC.javaHolder) } else { - fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIFINAGH: if let TIFINAGH = classObj.TIFINAGH { self.init(javaHolder: TIFINAGH.javaHolder) } else { - fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYLOTI_NAGRI: if let SYLOTI_NAGRI = classObj.SYLOTI_NAGRI { self.init(javaHolder: SYLOTI_NAGRI.javaHolder) } else { - fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERSIAN: if let OLD_PERSIAN = classObj.OLD_PERSIAN { self.init(javaHolder: OLD_PERSIAN.javaHolder) } else { - fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHAROSHTHI: if let KHAROSHTHI = classObj.KHAROSHTHI { self.init(javaHolder: KHAROSHTHI.javaHolder) } else { - fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BALINESE: if let BALINESE = classObj.BALINESE { self.init(javaHolder: BALINESE.javaHolder) } else { - fatalError("Enum value BALINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BALINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CUNEIFORM: if let CUNEIFORM = classObj.CUNEIFORM { self.init(javaHolder: CUNEIFORM.javaHolder) } else { - fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHOENICIAN: if let PHOENICIAN = classObj.PHOENICIAN { self.init(javaHolder: PHOENICIAN.javaHolder) } else { - fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHAGS_PA: if let PHAGS_PA = classObj.PHAGS_PA { self.init(javaHolder: PHAGS_PA.javaHolder) } else { - fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NKO: if let NKO = classObj.NKO { self.init(javaHolder: NKO.javaHolder) } else { - fatalError("Enum value NKO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NKO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SUNDANESE: if let SUNDANESE = classObj.SUNDANESE { self.init(javaHolder: SUNDANESE.javaHolder) } else { - fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BATAK: if let BATAK = classObj.BATAK { self.init(javaHolder: BATAK.javaHolder) } else { - fatalError("Enum value BATAK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BATAK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LEPCHA: if let LEPCHA = classObj.LEPCHA { self.init(javaHolder: LEPCHA.javaHolder) } else { - fatalError("Enum value LEPCHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LEPCHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OL_CHIKI: if let OL_CHIKI = classObj.OL_CHIKI { self.init(javaHolder: OL_CHIKI.javaHolder) } else { - fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VAI: if let VAI = classObj.VAI { self.init(javaHolder: VAI.javaHolder) } else { - fatalError("Enum value VAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAURASHTRA: if let SAURASHTRA = classObj.SAURASHTRA { self.init(javaHolder: SAURASHTRA.javaHolder) } else { - fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAYAH_LI: if let KAYAH_LI = classObj.KAYAH_LI { self.init(javaHolder: KAYAH_LI.javaHolder) } else { - fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .REJANG: if let REJANG = classObj.REJANG { self.init(javaHolder: REJANG.javaHolder) } else { - fatalError("Enum value REJANG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value REJANG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYCIAN: if let LYCIAN = classObj.LYCIAN { self.init(javaHolder: LYCIAN.javaHolder) } else { - fatalError("Enum value LYCIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYCIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CARIAN: if let CARIAN = classObj.CARIAN { self.init(javaHolder: CARIAN.javaHolder) } else { - fatalError("Enum value CARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYDIAN: if let LYDIAN = classObj.LYDIAN { self.init(javaHolder: LYDIAN.javaHolder) } else { - fatalError("Enum value LYDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAM: if let CHAM = classObj.CHAM { self.init(javaHolder: CHAM.javaHolder) } else { - fatalError("Enum value CHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_THAM: if let TAI_THAM = classObj.TAI_THAM { self.init(javaHolder: TAI_THAM.javaHolder) } else { - fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_VIET: if let TAI_VIET = classObj.TAI_VIET { self.init(javaHolder: TAI_VIET.javaHolder) } else { - fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AVESTAN: if let AVESTAN = classObj.AVESTAN { self.init(javaHolder: AVESTAN.javaHolder) } else { - fatalError("Enum value AVESTAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AVESTAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .EGYPTIAN_HIEROGLYPHS: if let EGYPTIAN_HIEROGLYPHS = classObj.EGYPTIAN_HIEROGLYPHS { self.init(javaHolder: EGYPTIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAMARITAN: if let SAMARITAN = classObj.SAMARITAN { self.init(javaHolder: SAMARITAN.javaHolder) } else { - fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANDAIC: if let MANDAIC = classObj.MANDAIC { self.init(javaHolder: MANDAIC.javaHolder) } else { - fatalError("Enum value MANDAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANDAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LISU: if let LISU = classObj.LISU { self.init(javaHolder: LISU.javaHolder) } else { - fatalError("Enum value LISU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LISU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BAMUM: if let BAMUM = classObj.BAMUM { self.init(javaHolder: BAMUM.javaHolder) } else { - fatalError("Enum value BAMUM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BAMUM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .JAVANESE: if let JAVANESE = classObj.JAVANESE { self.init(javaHolder: JAVANESE.javaHolder) } else { - fatalError("Enum value JAVANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value JAVANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEETEI_MAYEK: if let MEETEI_MAYEK = classObj.MEETEI_MAYEK { self.init(javaHolder: MEETEI_MAYEK.javaHolder) } else { - fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .IMPERIAL_ARAMAIC: if let IMPERIAL_ARAMAIC = classObj.IMPERIAL_ARAMAIC { self.init(javaHolder: IMPERIAL_ARAMAIC.javaHolder) } else { - fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOUTH_ARABIAN: if let OLD_SOUTH_ARABIAN = classObj.OLD_SOUTH_ARABIAN { self.init(javaHolder: OLD_SOUTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PARTHIAN: if let INSCRIPTIONAL_PARTHIAN = classObj.INSCRIPTIONAL_PARTHIAN { self.init(javaHolder: INSCRIPTIONAL_PARTHIAN.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PAHLAVI: if let INSCRIPTIONAL_PAHLAVI = classObj.INSCRIPTIONAL_PAHLAVI { self.init(javaHolder: INSCRIPTIONAL_PAHLAVI.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_TURKIC: if let OLD_TURKIC = classObj.OLD_TURKIC { self.init(javaHolder: OLD_TURKIC.javaHolder) } else { - fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAHMI: if let BRAHMI = classObj.BRAHMI { self.init(javaHolder: BRAHMI.javaHolder) } else { - fatalError("Enum value BRAHMI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAHMI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAITHI: if let KAITHI = classObj.KAITHI { self.init(javaHolder: KAITHI.javaHolder) } else { - fatalError("Enum value KAITHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAITHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_HIEROGLYPHS: if let MEROITIC_HIEROGLYPHS = classObj.MEROITIC_HIEROGLYPHS { self.init(javaHolder: MEROITIC_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_CURSIVE: if let MEROITIC_CURSIVE = classObj.MEROITIC_CURSIVE { self.init(javaHolder: MEROITIC_CURSIVE.javaHolder) } else { - fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SORA_SOMPENG: if let SORA_SOMPENG = classObj.SORA_SOMPENG { self.init(javaHolder: SORA_SOMPENG.javaHolder) } else { - fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAKMA: if let CHAKMA = classObj.CHAKMA { self.init(javaHolder: CHAKMA.javaHolder) } else { - fatalError("Enum value CHAKMA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAKMA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHARADA: if let SHARADA = classObj.SHARADA { self.init(javaHolder: SHARADA.javaHolder) } else { - fatalError("Enum value SHARADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHARADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAKRI: if let TAKRI = classObj.TAKRI { self.init(javaHolder: TAKRI.javaHolder) } else { - fatalError("Enum value TAKRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAKRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MIAO: if let MIAO = classObj.MIAO { self.init(javaHolder: MIAO.javaHolder) } else { - fatalError("Enum value MIAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MIAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CAUCASIAN_ALBANIAN: if let CAUCASIAN_ALBANIAN = classObj.CAUCASIAN_ALBANIAN { self.init(javaHolder: CAUCASIAN_ALBANIAN.javaHolder) } else { - fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BASSA_VAH: if let BASSA_VAH = classObj.BASSA_VAH { self.init(javaHolder: BASSA_VAH.javaHolder) } else { - fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DUPLOYAN: if let DUPLOYAN = classObj.DUPLOYAN { self.init(javaHolder: DUPLOYAN.javaHolder) } else { - fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELBASAN: if let ELBASAN = classObj.ELBASAN { self.init(javaHolder: ELBASAN.javaHolder) } else { - fatalError("Enum value ELBASAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELBASAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GRANTHA: if let GRANTHA = classObj.GRANTHA { self.init(javaHolder: GRANTHA.javaHolder) } else { - fatalError("Enum value GRANTHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GRANTHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAHAWH_HMONG: if let PAHAWH_HMONG = classObj.PAHAWH_HMONG { self.init(javaHolder: PAHAWH_HMONG.javaHolder) } else { - fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHOJKI: if let KHOJKI = classObj.KHOJKI { self.init(javaHolder: KHOJKI.javaHolder) } else { - fatalError("Enum value KHOJKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHOJKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_A: if let LINEAR_A = classObj.LINEAR_A { self.init(javaHolder: LINEAR_A.javaHolder) } else { - fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAHAJANI: if let MAHAJANI = classObj.MAHAJANI { self.init(javaHolder: MAHAJANI.javaHolder) } else { - fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANICHAEAN: if let MANICHAEAN = classObj.MANICHAEAN { self.init(javaHolder: MANICHAEAN.javaHolder) } else { - fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MENDE_KIKAKUI: if let MENDE_KIKAKUI = classObj.MENDE_KIKAKUI { self.init(javaHolder: MENDE_KIKAKUI.javaHolder) } else { - fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MODI: if let MODI = classObj.MODI { self.init(javaHolder: MODI.javaHolder) } else { - fatalError("Enum value MODI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MODI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MRO: if let MRO = classObj.MRO { self.init(javaHolder: MRO.javaHolder) } else { - fatalError("Enum value MRO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MRO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_NORTH_ARABIAN: if let OLD_NORTH_ARABIAN = classObj.OLD_NORTH_ARABIAN { self.init(javaHolder: OLD_NORTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NABATAEAN: if let NABATAEAN = classObj.NABATAEAN { self.init(javaHolder: NABATAEAN.javaHolder) } else { - fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PALMYRENE: if let PALMYRENE = classObj.PALMYRENE { self.init(javaHolder: PALMYRENE.javaHolder) } else { - fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAU_CIN_HAU: if let PAU_CIN_HAU = classObj.PAU_CIN_HAU { self.init(javaHolder: PAU_CIN_HAU.javaHolder) } else { - fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERMIC: if let OLD_PERMIC = classObj.OLD_PERMIC { self.init(javaHolder: OLD_PERMIC.javaHolder) } else { - fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PSALTER_PAHLAVI: if let PSALTER_PAHLAVI = classObj.PSALTER_PAHLAVI { self.init(javaHolder: PSALTER_PAHLAVI.javaHolder) } else { - fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIDDHAM: if let SIDDHAM = classObj.SIDDHAM { self.init(javaHolder: SIDDHAM.javaHolder) } else { - fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHUDAWADI: if let KHUDAWADI = classObj.KHUDAWADI { self.init(javaHolder: KHUDAWADI.javaHolder) } else { - fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIRHUTA: if let TIRHUTA = classObj.TIRHUTA { self.init(javaHolder: TIRHUTA.javaHolder) } else { - fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WARANG_CITI: if let WARANG_CITI = classObj.WARANG_CITI { self.init(javaHolder: WARANG_CITI.javaHolder) } else { - fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AHOM: if let AHOM = classObj.AHOM { self.init(javaHolder: AHOM.javaHolder) } else { - fatalError("Enum value AHOM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AHOM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ANATOLIAN_HIEROGLYPHS: if let ANATOLIAN_HIEROGLYPHS = classObj.ANATOLIAN_HIEROGLYPHS { self.init(javaHolder: ANATOLIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HATRAN: if let HATRAN = classObj.HATRAN { self.init(javaHolder: HATRAN.javaHolder) } else { - fatalError("Enum value HATRAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HATRAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MULTANI: if let MULTANI = classObj.MULTANI { self.init(javaHolder: MULTANI.javaHolder) } else { - fatalError("Enum value MULTANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MULTANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_HUNGARIAN: if let OLD_HUNGARIAN = classObj.OLD_HUNGARIAN { self.init(javaHolder: OLD_HUNGARIAN.javaHolder) } else { - fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIGNWRITING: if let SIGNWRITING = classObj.SIGNWRITING { self.init(javaHolder: SIGNWRITING.javaHolder) } else { - fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ADLAM: if let ADLAM = classObj.ADLAM { self.init(javaHolder: ADLAM.javaHolder) } else { - fatalError("Enum value ADLAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ADLAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BHAIKSUKI: if let BHAIKSUKI = classObj.BHAIKSUKI { self.init(javaHolder: BHAIKSUKI.javaHolder) } else { - fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MARCHEN: if let MARCHEN = classObj.MARCHEN { self.init(javaHolder: MARCHEN.javaHolder) } else { - fatalError("Enum value MARCHEN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MARCHEN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEWA: if let NEWA = classObj.NEWA { self.init(javaHolder: NEWA.javaHolder) } else { - fatalError("Enum value NEWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSAGE: if let OSAGE = classObj.OSAGE { self.init(javaHolder: OSAGE.javaHolder) } else { - fatalError("Enum value OSAGE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSAGE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGUT: if let TANGUT = classObj.TANGUT { self.init(javaHolder: TANGUT.javaHolder) } else { - fatalError("Enum value TANGUT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGUT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MASARAM_GONDI: if let MASARAM_GONDI = classObj.MASARAM_GONDI { self.init(javaHolder: MASARAM_GONDI.javaHolder) } else { - fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NUSHU: if let NUSHU = classObj.NUSHU { self.init(javaHolder: NUSHU.javaHolder) } else { - fatalError("Enum value NUSHU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NUSHU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOYOMBO: if let SOYOMBO = classObj.SOYOMBO { self.init(javaHolder: SOYOMBO.javaHolder) } else { - fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ZANABAZAR_SQUARE: if let ZANABAZAR_SQUARE = classObj.ZANABAZAR_SQUARE { self.init(javaHolder: ZANABAZAR_SQUARE.javaHolder) } else { - fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANIFI_ROHINGYA: if let HANIFI_ROHINGYA = classObj.HANIFI_ROHINGYA { self.init(javaHolder: HANIFI_ROHINGYA.javaHolder) } else { - fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOGDIAN: if let OLD_SOGDIAN = classObj.OLD_SOGDIAN { self.init(javaHolder: OLD_SOGDIAN.javaHolder) } else { - fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOGDIAN: if let SOGDIAN = classObj.SOGDIAN { self.init(javaHolder: SOGDIAN.javaHolder) } else { - fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DOGRA: if let DOGRA = classObj.DOGRA { self.init(javaHolder: DOGRA.javaHolder) } else { - fatalError("Enum value DOGRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DOGRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUNJALA_GONDI: if let GUNJALA_GONDI = classObj.GUNJALA_GONDI { self.init(javaHolder: GUNJALA_GONDI.javaHolder) } else { - fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAKASAR: if let MAKASAR = classObj.MAKASAR { self.init(javaHolder: MAKASAR.javaHolder) } else { - fatalError("Enum value MAKASAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAKASAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEDEFAIDRIN: if let MEDEFAIDRIN = classObj.MEDEFAIDRIN { self.init(javaHolder: MEDEFAIDRIN.javaHolder) } else { - fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELYMAIC: if let ELYMAIC = classObj.ELYMAIC { self.init(javaHolder: ELYMAIC.javaHolder) } else { - fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NANDINAGARI: if let NANDINAGARI = classObj.NANDINAGARI { self.init(javaHolder: NANDINAGARI.javaHolder) } else { - fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NYIAKENG_PUACHUE_HMONG: if let NYIAKENG_PUACHUE_HMONG = classObj.NYIAKENG_PUACHUE_HMONG { self.init(javaHolder: NYIAKENG_PUACHUE_HMONG.javaHolder) } else { - fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WANCHO: if let WANCHO = classObj.WANCHO { self.init(javaHolder: WANCHO.javaHolder) } else { - fatalError("Enum value WANCHO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WANCHO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YEZIDI: if let YEZIDI = classObj.YEZIDI { self.init(javaHolder: YEZIDI.javaHolder) } else { - fatalError("Enum value YEZIDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YEZIDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHORASMIAN: if let CHORASMIAN = classObj.CHORASMIAN { self.init(javaHolder: CHORASMIAN.javaHolder) } else { - fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DIVES_AKURU: if let DIVES_AKURU = classObj.DIVES_AKURU { self.init(javaHolder: DIVES_AKURU.javaHolder) } else { - fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHITAN_SMALL_SCRIPT: if let KHITAN_SMALL_SCRIPT = classObj.KHITAN_SMALL_SCRIPT { self.init(javaHolder: KHITAN_SMALL_SCRIPT.javaHolder) } else { - fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VITHKUQI: if let VITHKUQI = classObj.VITHKUQI { self.init(javaHolder: VITHKUQI.javaHolder) } else { - fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_UYGHUR: if let OLD_UYGHUR = classObj.OLD_UYGHUR { self.init(javaHolder: OLD_UYGHUR.javaHolder) } else { - fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRO_MINOAN: if let CYPRO_MINOAN = classObj.CYPRO_MINOAN { self.init(javaHolder: CYPRO_MINOAN.javaHolder) } else { - fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGSA: if let TANGSA = classObj.TANGSA { self.init(javaHolder: TANGSA.javaHolder) } else { - fatalError("Enum value TANGSA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGSA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TOTO: if let TOTO = classObj.TOTO { self.init(javaHolder: TOTO.javaHolder) } else { - fatalError("Enum value TOTO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TOTO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAWI: if let KAWI = classObj.KAWI { self.init(javaHolder: KAWI.javaHolder) } else { - fatalError("Enum value KAWI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAWI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NAG_MUNDARI: if let NAG_MUNDARI = classObj.NAG_MUNDARI { self.init(javaHolder: NAG_MUNDARI.javaHolder) } else { - fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UNKNOWN: if let UNKNOWN = classObj.UNKNOWN { self.init(javaHolder: UNKNOWN.javaHolder) } else { - fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run swift-java on the most updated Java class") } } } diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 0f1af1cd..a1147e3c 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import CSwiftJavaJNI -@JavaClass("java.lang.Class") +@JavaClass("java.lang.Class", implements: JavaReflectType.self) open class JavaClass: JavaObject { @JavaMethod open func getName() -> String @@ -52,13 +52,16 @@ open class JavaClass: JavaObject { open func isRecord() -> Bool @JavaMethod - open func getClassLoader() -> JavaClassLoader! + open func isSealed() -> Bool @JavaMethod - open func newInstance() throws -> JavaObject! + open func getInterfaces() -> [JavaClass?] @JavaMethod - open func getInterfaces() -> [JavaClass?] + open func getClassLoader() -> JavaClassLoader! + + @JavaMethod + open func newInstance() throws -> T! @JavaMethod open func isMemberClass() -> Bool @@ -70,7 +73,7 @@ open class JavaClass: JavaObject { open func isAnonymousClass() -> Bool @JavaMethod - open func getEnclosingClass() throws -> JavaClass! + open func getEnclosingClass() -> JavaClass! @JavaMethod open func arrayType() -> JavaClass! @@ -81,6 +84,9 @@ open class JavaClass: JavaObject { @JavaMethod open func getCanonicalName() -> String + @JavaMethod + open func getDeclaredClasses() -> [JavaClass?] + @JavaMethod open func getPackageName() -> String @@ -102,11 +108,17 @@ open class JavaClass: JavaObject { @JavaMethod open func isSynthetic() -> Bool + @JavaMethod + open func getGenericSuperclass() -> JavaReflectType! + + @JavaMethod + open func getGenericInterfaces() -> [JavaReflectType?] + @JavaMethod open func getSigners() -> [JavaObject?] @JavaMethod - open func getDeclaringClass() throws -> JavaClass! + open func getDeclaringClass() -> JavaClass! @JavaMethod open func getTypeName() -> String @@ -114,9 +126,6 @@ open class JavaClass: JavaObject { @JavaMethod open func getClasses() -> [JavaClass?] - @JavaMethod - open func getDeclaredClasses() throws -> [JavaClass?] - @JavaMethod open func getEnumConstants() -> [JavaObject?] @@ -128,16 +137,13 @@ open class JavaClass: JavaObject { @JavaMethod open func getNestMembers() -> [JavaClass?] - - @JavaMethod - open func isSealed() -> Bool } extension JavaClass { @JavaStaticMethod - public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod - public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod public func forPrimitiveName(_ arg0: String) -> JavaClass! where ObjectType == JavaClass diff --git a/Sources/SwiftJava/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift index 349cba8d..0cd64aa1 100644 --- a/Sources/SwiftJava/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -7,10 +7,10 @@ open class JavaClassLoader: JavaObject { open func getName() -> String @JavaMethod - open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! + open func loadClass(_ arg0: String) throws -> JavaClass! @JavaMethod - open func loadClass(_ arg0: String) throws -> JavaClass! + open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! @JavaMethod open func setSigners(_ arg0: JavaClass?, _ arg1: [JavaObject?]) @@ -22,10 +22,10 @@ open class JavaClassLoader: JavaObject { open func findLoadedClass(_ arg0: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String) throws -> JavaClass! + open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! + open func findClass(_ arg0: String) throws -> JavaClass! @JavaMethod open func resolveClass(_ arg0: JavaClass?) diff --git a/Sources/SwiftJava/generated/JavaEnum.swift b/Sources/SwiftJava/generated/JavaEnum.swift new file mode 100644 index 00000000..2b8e102c --- /dev/null +++ b/Sources/SwiftJava/generated/JavaEnum.swift @@ -0,0 +1,11 @@ +// // Auto-generated by Java-to-Swift wrapper generator. +// import CSwiftJavaJNI + +// @JavaClass("java.lang.Enum") +// open class JavaEnum: JavaObject { +// @JavaMethod +// public func name() -> String + +// @JavaMethod +// public func ordinal() -> Int32 +// } diff --git a/Sources/SwiftJava/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift index 94800037..df57ba66 100644 --- a/Sources/SwiftJava/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -3,7 +3,6 @@ import CSwiftJavaJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { - @JavaMethod @_nonoverride public convenience init(_ arg0: Int32, environment: JNIEnvironment? = nil) @@ -121,10 +120,10 @@ extension JavaClass { public func valueOf(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! + public func valueOf(_ arg0: Int32) -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: Int32) -> JavaInteger! + public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! @JavaStaticMethod public func toHexString(_ arg0: Int32) -> String @@ -133,31 +132,37 @@ extension JavaClass { public func decode(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func parseInt(_ arg0: String) throws -> Int32 + public func parseInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func parseInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func toUnsignedLong(_ arg0: Int32) -> Int64 + public func parseInt(_ arg0: String) throws -> Int32 @JavaStaticMethod - public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 + public func highestOneBit(_ arg0: Int32) -> Int32 @JavaStaticMethod - public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String + public func toUnsignedLong(_ arg0: Int32) -> Int64 + + @JavaStaticMethod + public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 @JavaStaticMethod public func toUnsignedString(_ arg0: Int32) -> String @JavaStaticMethod - public func parseUnsignedInt(_ arg0: String) throws -> Int32 + public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String @JavaStaticMethod public func parseUnsignedInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! + public func parseUnsignedInt(_ arg0: String) throws -> Int32 + + @JavaStaticMethod + public func parseUnsignedInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func getInteger(_ arg0: String, _ arg1: Int32) -> JavaInteger! @@ -166,13 +171,13 @@ extension JavaClass { public func getInteger(_ arg0: String) -> JavaInteger! @JavaStaticMethod - public func toOctalString(_ arg0: Int32) -> String + public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! @JavaStaticMethod - public func toBinaryString(_ arg0: Int32) -> String + public func toOctalString(_ arg0: Int32) -> String @JavaStaticMethod - public func highestOneBit(_ arg0: Int32) -> Int32 + public func toBinaryString(_ arg0: Int32) -> String @JavaStaticMethod public func lowestOneBit(_ arg0: Int32) -> Int32 diff --git a/Sources/SwiftJava/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift index a986e9ef..7ea8fc09 100644 --- a/Sources/SwiftJava/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -126,10 +126,10 @@ extension JavaClass { public func compare(_ arg0: Int64, _ arg1: Int64) -> Int32 @JavaStaticMethod - public func valueOf(_ arg0: String) throws -> JavaLong! + public func valueOf(_ arg0: Int64) -> JavaLong! @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> JavaLong! + public func valueOf(_ arg0: String) throws -> JavaLong! @JavaStaticMethod public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaLong! @@ -140,6 +140,9 @@ extension JavaClass { @JavaStaticMethod public func decode(_ arg0: String) throws -> JavaLong! + @JavaStaticMethod + public func highestOneBit(_ arg0: Int64) -> Int64 + @JavaStaticMethod public func sum(_ arg0: Int64, _ arg1: Int64) -> Int64 @@ -155,9 +158,6 @@ extension JavaClass { @JavaStaticMethod public func toBinaryString(_ arg0: Int64) -> String - @JavaStaticMethod - public func highestOneBit(_ arg0: Int64) -> Int64 - @JavaStaticMethod public func lowestOneBit(_ arg0: Int64) -> Int64 @@ -168,20 +168,20 @@ extension JavaClass { public func rotateRight(_ arg0: Int64, _ arg1: Int32) -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: String) throws -> Int64 + public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseUnsignedLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseUnsignedLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: String) throws -> Int64 + public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 } diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 08cc764a..5f10005f 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -4,7 +4,7 @@ import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { @JavaMethod - open func get() -> JavaObject! + open func get() -> JavaObject! // FIXME: Currently we do generate -> T https://github.com/swiftlang/swift-java/issues/439 @JavaMethod open override func equals(_ arg0: JavaObject?) -> Bool diff --git a/Sources/SwiftJava/generated/JavaReflectArray.swift b/Sources/SwiftJava/generated/JavaReflectArray.swift new file mode 100644 index 00000000..4cae1202 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectArray.swift @@ -0,0 +1,71 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.lang.reflect.Array") +open class JavaReflectArray: JavaObject { + +} +extension JavaClass { + @JavaStaticMethod + public func get(_ arg0: JavaObject?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func getLength(_ arg0: JavaObject?) throws -> Int32 + + @JavaStaticMethod + public func getBoolean(_ arg0: JavaObject?, _ arg1: Int32) throws -> Bool + + @JavaStaticMethod + public func getByte(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int8 + + @JavaStaticMethod + public func getShort(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int16 + + @JavaStaticMethod + public func getChar(_ arg0: JavaObject?, _ arg1: Int32) throws -> UInt16 + + @JavaStaticMethod + public func getInt(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int32 + + @JavaStaticMethod + public func getLong(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int64 + + @JavaStaticMethod + public func getFloat(_ arg0: JavaObject?, _ arg1: Int32) throws -> Float + + @JavaStaticMethod + public func getDouble(_ arg0: JavaObject?, _ arg1: Int32) throws -> Double + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: [Int32]) throws -> JavaObject! + + @JavaStaticMethod + public func set(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: JavaObject?) throws + + @JavaStaticMethod + public func setBoolean(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Bool) throws + + @JavaStaticMethod + public func setByte(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int8) throws + + @JavaStaticMethod + public func setChar(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: UInt16) throws + + @JavaStaticMethod + public func setShort(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int16) throws + + @JavaStaticMethod + public func setInt(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int32) throws + + @JavaStaticMethod + public func setLong(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int64) throws + + @JavaStaticMethod + public func setFloat(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Float) throws + + @JavaStaticMethod + public func setDouble(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Double) throws +} diff --git a/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift new file mode 100644 index 00000000..a0801644 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift @@ -0,0 +1,17 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaInterface("java.lang.reflect.ParameterizedType", extends: JavaReflectType.self) +public struct JavaReflectParameterizedType { + @JavaMethod + public func getOwnerType() -> JavaReflectType! + + @JavaMethod + public func getRawType() -> JavaReflectType! + + @JavaMethod + public func getActualTypeArguments() -> [JavaReflectType?] + + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/generated/JavaReflectType.swift b/Sources/SwiftJava/generated/JavaReflectType.swift new file mode 100644 index 00000000..fdf3b572 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectType.swift @@ -0,0 +1,8 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaInterface("java.lang.reflect.Type") +public struct JavaReflectType { + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index b45671a7..d07ff162 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -5,6 +5,8 @@ "java.lang.Byte" : "JavaByte", "java.lang.Character" : "JavaCharacter", "java.lang.Class" : "JavaClass", + "java.lang.reflect.Type" : "JavaReflectType", + "java.lang.reflect.ParameterizedType" : "JavaReflectParameterizedType", "java.lang.ClassLoader" : "JavaClassLoader", "java.lang.Double" : "JavaDouble", "java.lang.Error" : "JavaError", diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index c9d0cedf..0fb5494f 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -65,7 +65,7 @@ public struct Configuration: Codable { asyncFuncMode ?? .default } - // ==== java 2 swift --------------------------------------------------------- + // ==== wrap-java --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. public var classpath: String? = nil @@ -85,6 +85,12 @@ public struct Configuration: Codable { // Generate class files suitable for the specified Java SE release. public var targetCompatibility: JavaVersion? + /// Filter input Java types by their package prefix if set. + public var filterInclude: [String]? + + /// Exclude input Java types by their package prefix or exact match. + public var filterExclude: [String]? + // ==== dependencies --------------------------------------------------------- // Java dependencies we need to fetch for this target. diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 9a05a286..837d5e2f 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -72,7 +72,7 @@ extension SwiftJava.ConfigureCommand { let jvm = try self.makeJVM(classpathEntries: classpathEntries) - try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment()) + try emitConfiguration(classpathEntries: classpathEntries, environment: jvm.environment()) } /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration. @@ -97,32 +97,41 @@ extension SwiftJava.ConfigureCommand { // TODO: make this perhaps "emit type mappings" mutating func emitConfiguration( - classpath: [String], + classpathEntries: [String], environment: JNIEnvironment ) throws { - if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage { - print("[java-swift][debug] Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)") + var log = Self.log + log.logLevel = .init(rawValue: self.logLevel.rawValue)! + + log.info("Run: emit configuration...") + var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() + + if !self.commonOptions.filterInclude.isEmpty { + log.debug("Generate Java->Swift type mappings. Active include filters: \(self.commonOptions.filterInclude)") + } else if let filters = configuration.filterInclude, !filters.isEmpty { + // take the package filter from the configuration file + self.commonOptions.filterInclude = filters + } else { + log.debug("Generate Java->Swift type mappings. No package include filter applied.") } - print("[java-swift][debug] Classpath: \(classpath)") + log.debug("Classpath: \(classpathEntries)") - if classpath.isEmpty { - print("[java-swift][warning] Classpath is empty!") + if classpathEntries.isEmpty { + log.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...") + log.info("Amend existing swift-java.config file...") } - configuration.classpath = classpath.joined(separator: ":") // TODO: is this correct? + configuration.classpath = classpathEntries.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)") + log.debug("Classpath entry does not exist: \(entry)") continue } @@ -135,9 +144,9 @@ extension SwiftJava.ConfigureCommand { environment: environment ) } else if FileManager.default.fileExists(atPath: entry) { - print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") + log.warning("Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") } else { - print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)") + log.warning("Classpath entry does not exist, skipping: \(entry)") } } @@ -158,6 +167,8 @@ extension SwiftJava.ConfigureCommand { forJar jarFile: JarFile, environment: JNIEnvironment ) throws { + let log = Self.log + for entry in jarFile.entries()! { // We only look at class files in the Jar file. guard entry.getName().hasSuffix(".class") else { @@ -183,9 +194,8 @@ extension SwiftJava.ConfigureCommand { 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 + guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else { + log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters") continue } @@ -195,7 +205,17 @@ extension SwiftJava.ConfigureCommand { continue } - configuration.classes?[javaCanonicalName] = + if configuration.classes == nil { + configuration.classes = [:] + } + + if let configuredSwiftName = configuration.classes![javaCanonicalName] { + log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.") + } else { + log.info("Configure Java type '\(javaCanonicalName)' as '\(javaCanonicalName.defaultSwiftNameForJavaClass.bold)' Swift type.") + } + + configuration.classes![javaCanonicalName] = javaCanonicalName.defaultSwiftNameForJavaClass } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index b5942d2a..b5c3a7bb 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -86,9 +86,7 @@ extension SwiftJava { extension SwiftJava.JExtractCommand { func runSwiftJavaCommand(config: inout Configuration) async throws { - if let javaPackage { - config.javaPackage = javaPackage - } + configure(&config.javaPackage, overrideWith: self.javaPackage) configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava @@ -136,12 +134,6 @@ extension SwiftJava.JExtractCommand { } } } - - func configure(_ setting: inout T?, overrideWith value: T?) { - if let value { - setting = value - } - } } struct IncompatibleModeError: Error { diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 58c64690..7cc4c477 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -104,7 +104,10 @@ extension SwiftJava.ResolveCommand { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") - let dependenciesClasspath = await resolveDependencies(dependencies: dependencies) + let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + .appendingPathComponent(".build") + + let dependenciesClasspath = await resolveDependencies(workDir: workDir, dependencies: dependencies) let classpathEntries = dependenciesClasspath.split(separator: ":") print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "") @@ -122,10 +125,15 @@ extension SwiftJava.ResolveCommand { /// /// - Parameter dependencies: maven-style dependencies to resolve /// - Returns: Colon-separated classpath - func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String { - let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - .appendingPathComponent(".build") - let resolverDir = try! createTemporaryDirectory(in: workDir) + func resolveDependencies(workDir: URL, dependencies: [JavaDependencyDescriptor]) async -> String { + print("Create directory: \(workDir.absoluteString)") + + let resolverDir: URL + do { + resolverDir = try createTemporaryDirectory(in: workDir) + } catch { + fatalError("Unable to create temp directory at: \(workDir.absoluteString)! \(error)") + } defer { try? FileManager.default.removeItem(at: resolverDir) } @@ -162,7 +170,9 @@ extension SwiftJava.ResolveCommand { } 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 ?? "")>>>") + "Command was: \(CommandLine.arguments.joined(separator: " ").bold)\n" + + "Output was: <<<\(outString)>>>;\n" + + "Err was: <<<\(errString)>>>") } return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 43362edb..ae985e77 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -66,6 +66,9 @@ extension SwiftJava { extension SwiftJava.WrapJavaCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + configure(&config.filterInclude, append: self.commonOptions.filterInclude) + configure(&config.filterExclude, append: self.commonOptions.filterExclude) + // Get base classpath configuration for this target and configuration var classpathSearchDirs = [self.effectiveSwiftModuleURL] if let cacheDir = self.cacheDirectory { @@ -113,16 +116,19 @@ extension SwiftJava.WrapJavaCommand { extension SwiftJava.WrapJavaCommand { mutating func generateWrappers( config: Configuration, - // classpathEntries: [String], dependentConfigs: [(String, Configuration)], environment: JNIEnvironment ) throws { let translator = JavaTranslator( + config: config, swiftModuleName: effectiveSwiftModule, environment: environment, translateAsClass: true ) + log.info("Active include filters: \(config.filterInclude ?? [])") + log.info("Active exclude filters: \(config.filterExclude ?? [])") + // Keep track of all of the Java classes that will have // Swift-native implementations. translator.swiftNativeImplementations = Set(swiftNativeImplementation) @@ -139,12 +145,30 @@ extension SwiftJava.WrapJavaCommand { translator.addConfiguration(config, forSwiftModule: effectiveSwiftModule) // Load all of the explicitly-requested classes. - let classLoader = try JavaClass(environment: environment) + let classLoader = try! JavaClass(environment: environment) .getSystemClassLoader()! var javaClasses: [JavaClass] = [] - for (javaClassName, _) in config.classes ?? [:] { + eachClass: for (javaClassName, _) in config.classes ?? [:] { + + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") + continue + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + continue eachClass + } + } + + log.info("Wrapping java type: \(javaClassName)") + guard let javaClass = try classLoader.loadClass(javaClassName) else { - print("warning: could not find Java class '\(javaClassName)'") + log.warning("Could not load Java class '\(javaClassName)', skipping.") continue } @@ -178,8 +202,23 @@ extension SwiftJava.WrapJavaCommand { return nil } + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") + return nil + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + return nil + } + } + // If this class has been explicitly mentioned, we're done. - if translator.translatedClasses[javaClassName] != nil { + guard translator.translatedClasses[javaClassName] == nil else { return nil } @@ -187,9 +226,8 @@ extension SwiftJava.WrapJavaCommand { let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName .defaultSwiftNameForJavaClass - let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)" - translator.translatedClasses[javaClassName] = (swiftName, nil) + translator.translatedClasses[javaClassName] = SwiftTypeName(module: nil, name: swiftName) return nestedClass } diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index e8aee62d..ebdfdbea 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -30,6 +30,18 @@ protocol HasCommonOptions { var commonOptions: SwiftJava.CommonOptions { get set } } extension HasCommonOptions { + func configure(_ setting: inout T?, overrideWith value: T?) { + if let value { + setting = value + } + } + + func configure(_ setting: inout [T]?, append value: [T]?) { + if let value { + setting?.append(contentsOf: value) + } + } + var outputDirectory: String? { self.commonOptions.outputDirectory } @@ -45,6 +57,12 @@ extension SwiftJava { @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") var logLevel: JExtractSwiftLib.Logger.Level = .info + + @Option(name: .long, help: "While scanning a classpath, inspect ONLY types included in these packages") + var filterInclude: [String] = [] + + @Option(name: .long, help: "While scanning a classpath, skip types which match the filter prefix") + var filterExclude: [String] = [] } struct CommonJVMOptions: ParsableArguments { @@ -53,9 +71,6 @@ extension SwiftJava { 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 } } diff --git a/Sources/SwiftJavaTool/ExcludedJDKTypes.swift b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift new file mode 100644 index 00000000..1d24022f --- /dev/null +++ b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension SwiftJava { + /// Some types we cannot handle importing, so we hardcode skipping them. + public static let ExcludedJDKTypes: Set = [ + "java.lang.Enum", + "java.lang.Enum$EnumDesc", + ] + + static func shouldImport(javaCanonicalName: String, commonOptions: SwiftJava.CommonOptions) -> Bool { + if SwiftJava.ExcludedJDKTypes.contains(javaCanonicalName) { + return false + } + + for include in commonOptions.filterInclude { + guard javaCanonicalName.hasPrefix(include) else { + // Skip classes which don't match our expected prefix + return false + } + } + + for exclude in commonOptions.filterExclude { + if javaCanonicalName.hasPrefix(exclude) { + return false + } + } + + return true + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index d465a206..27650885 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -17,6 +17,7 @@ import SwiftJavaShared import CSwiftJavaJNI import SwiftJava +// FIXME: do we need this here or can we rely on the generated one? @JavaClass("java.lang.ClassLoader") public struct ClassLoader { @JavaMethod diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index a7c4d76f..795639e5 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -15,6 +15,8 @@ import SwiftJava import JavaLangReflect import SwiftSyntax +import SwiftJavaConfigurationShared +import Logging /// Utility type that translates a single Java class into its corresponding /// Swift type and any additional helper types or functions. @@ -23,6 +25,10 @@ struct JavaClassTranslator { /// needed for translation. let translator: JavaTranslator + var log: Logger { + translator.log + } + /// The Java class (or interface) being translated. let javaClass: JavaClass @@ -47,7 +53,7 @@ struct JavaClassTranslator { let effectiveJavaSuperclass: JavaClass? /// The Swift name of the superclass. - let swiftSuperclass: String? + let swiftSuperclass: SwiftJavaParameterizedType? /// The Swift names of the interfaces that this class implements. let swiftInterfaces: [String] @@ -98,16 +104,16 @@ struct JavaClassTranslator { } /// The generic parameter clause for the Swift version of the Java class. - var genericParameterClause: String { + var genericParameters: [String] { if javaTypeParameters.isEmpty { - return "" + return [] } let genericParameters = javaTypeParameters.map { param in "\(param.getName()): AnyJavaObject" } - return "<\(genericParameters.joined(separator: ", "))>" + return genericParameters } /// Prepare translation for the given Java class (or interface). @@ -126,23 +132,38 @@ struct JavaClassTranslator { self.javaTypeParameters = javaClass.getTypeParameters().compactMap { $0 } self.nestedClasses = translator.nestedClasses[fullName] ?? [] - // Superclass. + // Superclass, incl parameter types (if any) if !javaClass.isInterface() { var javaSuperclass = javaClass.getSuperclass() - var swiftSuperclass: String? = nil + var javaGenericSuperclass: JavaReflectType? = javaClass.getGenericSuperclass() + var swiftSuperclassName: String? = nil + var swiftSuperclassTypeArgs: [String] = [] while let javaSuperclassNonOpt = javaSuperclass { do { - swiftSuperclass = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + swiftSuperclassName = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + if let javaGenericSuperclass = javaGenericSuperclass?.as(JavaReflectParameterizedType.self) { + for typeArg in javaGenericSuperclass.getActualTypeArguments() { + let javaTypeArgName = typeArg?.getTypeName() ?? "" + if let swiftTypeArgName = self.translator.translatedClasses[javaTypeArgName] { + swiftSuperclassTypeArgs.append(swiftTypeArgName.qualifiedName) + } else { + swiftSuperclassTypeArgs.append("/* MISSING MAPPING FOR */ \(javaTypeArgName)") + } + } + } break } catch { translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") } javaSuperclass = javaSuperclassNonOpt.getSuperclass() + javaGenericSuperclass = javaClass.getGenericSuperclass() } self.effectiveJavaSuperclass = javaSuperclass - self.swiftSuperclass = swiftSuperclass + self.swiftSuperclass = SwiftJavaParameterizedType( + name: swiftSuperclassName, + typeArguments: swiftSuperclassTypeArgs) } else { self.effectiveJavaSuperclass = nil self.swiftSuperclass = nil @@ -192,8 +213,9 @@ struct JavaClassTranslator { for method in methods { guard let method else { continue } - // Only look at public and protected methods here. - guard method.isPublic || method.isProtected else { continue } + guard shouldExtract(method: method) else { + continue + } // Skip any methods that are expected to be implemented in Swift. We will // visit them in the second pass, over the *declared* methods, because @@ -226,6 +248,20 @@ struct JavaClassTranslator { /// MARK: Collection of Java class members. extension JavaClassTranslator { + + /// Determines whether a method should be extracted for translation. + /// Only look at public and protected methods here. + private func shouldExtract(method: Method) -> Bool { + switch self.translator.config.effectiveMinimumInputAccessLevelMode { + case .internal: + return method.isPublic || method.isProtected || method.isPackage + case .package: + return method.isPublic || method.isProtected || method.isPackage + case .public: + return method.isPublic || method.isProtected + } + } + /// Add a field to the appropriate lists(s) for later translation. private mutating func addField(_ field: Field) { // Static fields go into a separate list. @@ -325,13 +361,25 @@ extension JavaClassTranslator { // Compute the "extends" clause for the superclass (of the struct // formulation) or the inheritance clause (for the class // formulation). - let extends: String + let extendsClause: String let inheritanceClause: String if translateAsClass { - extends = "" - inheritanceClause = swiftSuperclass.map { ": \($0)" } ?? "" + extendsClause = "" + inheritanceClause = + if let swiftSuperclass, swiftSuperclass.typeArguments.isEmpty { + ": \(swiftSuperclass.name)" + } else if let swiftSuperclass { + ": \(swiftSuperclass.name)<\(swiftSuperclass.typeArguments.joined(separator: ", "))>" + } else { + "" + } } else { - extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" + extendsClause = + if let swiftSuperclass { + ", extends: \(swiftSuperclass.render()).self" + } else { + "" + } inheritanceClause = "" } @@ -344,12 +392,19 @@ extension JavaClassTranslator { interfacesStr = ", \(prefix): \(swiftInterfaces.map { "\($0).self" }.joined(separator: ", "))" } + let genericParameterClause = + if genericParameters.isEmpty { + "" + } else { + "<\(genericParameters.joined(separator: ", "))>" + } + // Emit the struct declaration describing the java class. let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; let introducer = translateAsClass ? "open class" : "public struct" var classDecl: DeclSyntax = """ - @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) + @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extendsClause)\(raw: interfacesStr)) \(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause)\(raw: inheritanceClause) { \(raw: members.map { $0.description }.joined(separator: "\n\n")) } @@ -396,7 +451,7 @@ extension JavaClassTranslator { } let genericArgumentClause = "<\(genericParameterNames.joined(separator: ", "))>" - staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" + staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" // FIXME: move the 'where ...' part into the render bit } else { staticMemberWhereClause = "" } @@ -418,7 +473,7 @@ extension JavaClassTranslator { do { return try renderMethod( method, implementedInSwift: /*FIXME:*/false, - genericParameterClause: genericParameterClause, + genericParameters: genericParameters, whereClause: staticMemberWhereClause ) } catch { @@ -435,7 +490,7 @@ extension JavaClassTranslator { // Specify the specialization arguments when needed. let extSpecialization: String - if genericParameterClause.isEmpty { + if genericParameters.isEmpty { extSpecialization = "<\(swiftTypeName)>" } else { extSpecialization = "" @@ -523,31 +578,76 @@ extension JavaClassTranslator { let accessModifier = javaConstructor.isPublic ? "public " : "" let convenienceModifier = translateAsClass ? "convenience " : "" let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : "" + + // FIXME: handle generics in constructors return """ @JavaMethod \(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) """ } + func genericParameterIsUsedInSignature(_ typeParam: TypeVariable, in method: Method) -> Bool { + // --- Return type + // Is the return type exactly the type param + // FIXME: make this equals based? + if method.getGenericReturnType().getTypeName() == typeParam.getTypeName() { + return true + } + + if let parameterizedReturnType = method.getGenericReturnType().as(ParameterizedType.self) { + for actualTypeParam in parameterizedReturnType.getActualTypeArguments() { + guard let actualTypeParam else { continue } + if actualTypeParam.isEqualTo(typeParam.as(Type.self)) { + return true + } + } + } + + return false + } + /// Translates the given Java method into a Swift declaration. package func renderMethod( _ javaMethod: Method, implementedInSwift: Bool, - genericParameterClause: String = "", + genericParameters: [String] = [], whereClause: String = "" ) throws -> DeclSyntax { - // Map the parameters. - let parameters = try translateJavaParameters(javaMethod.getParameters()) + // Map the generic params on the method. + var allGenericParameters = genericParameters + let typeParameters = javaMethod.getTypeParameters() + if typeParameters.contains(where: {$0 != nil }) { + allGenericParameters += typeParameters.compactMap { typeParam in + guard let typeParam else { return nil } + guard genericParameterIsUsedInSignature(typeParam, in: javaMethod) else { + return nil + } + return "\(typeParam.getTypeName()): AnyJavaObject" + } + } + let genericParameterClauseStr = + if allGenericParameters.isEmpty { + "" + } else { + "<\(allGenericParameters.joined(separator: ", "))>" + } + // Map the parameters. + let parameters = try translateJavaParameters(javaMethod) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") // Map the result type. let resultTypeStr: String - let resultType = try translator.getSwiftTypeNameAsString( - javaMethod.getGenericReturnType()!, - preferValueTypes: true, + let resultType = try translator.getSwiftReturnTypeNameAsString( + method: javaMethod, + preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) + // let resultType = try translator.getSwiftTypeNameAsString( + // javaMethod.getGenericReturnType()!, + // preferValueTypes: true, + // outerOptional: .implicitlyUnwrappedOptional + // ) // FIXME: cleanup the checking here if resultType != "Void" && resultType != "Swift.Void" { @@ -556,6 +656,7 @@ extension JavaClassTranslator { resultTypeStr = "" } + // --- Handle other effects let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" let swiftMethodName = javaMethod.getName().escapedSwiftName let methodAttribute: AttributeSyntax = implementedInSwift @@ -581,23 +682,24 @@ extension JavaClassTranslator { let resultOptional: String = resultType.optionalWrappedType() ?? resultType let baseBody: ExprSyntax = "\(raw: javaMethod.throwsCheckedException ? "try " : "")\(raw: swiftMethodName)(\(raw: parameters.map(\.passedArg).joined(separator: ", ")))" - let body: ExprSyntax = if let optionalType = resultType.optionalWrappedType() { - "Optional(javaOptional: \(baseBody))" - } else { - baseBody - } + let body: ExprSyntax = + if resultType.optionalWrappedType() != nil { + "Optional(javaOptional: \(baseBody))" + } else { + baseBody + } return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) - \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClause)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { + \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClauseStr)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { \(body) } """ } else { return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ } } @@ -688,7 +790,7 @@ extension JavaClassTranslator { ? "self.init(javaHolder: \($0.getName()).javaHolder)" : "self = \($0.getName())") } else { - fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run swift-java on the most updated Java class") } """ }.joined(separator: "\n")) @@ -700,7 +802,30 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateJavaParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + private func translateJavaParameters( + _ javaMethod: JavaLangReflect.Method + ) throws -> [FunctionParameterSyntax] { + let parameters: [Parameter?] = javaMethod.getParameters() + + return try parameters.compactMap { javaParameter in + guard let javaParameter else { return nil } + + let typeName = try translator.getSwiftTypeNameAsString( + method: javaMethod, + javaParameter.getParameterizedType()!, + preferValueTypes: true, + outerOptional: .optional + ) + let paramName = javaParameter.getName() + return "_ \(raw: paramName): \(raw: typeName)" + } + } + + // Translate a Java parameter list into Swift parameters. + @available(*, deprecated, message: "Prefer the method based version") // FIXME: constructors are not well handled + private func translateJavaParameters( + _ parameters: [Parameter?] + ) throws -> [FunctionParameterSyntax] { return try parameters.compactMap { javaParameter in guard let javaParameter else { return nil } @@ -778,9 +903,7 @@ extension JavaClassTranslator { continue } - // Ignore non-public, non-protected methods because they would not - // have been render into the Swift superclass. - if !overriddenMethod.isPublic && !overriddenMethod.isProtected { + guard shouldExtract(method: overriddenMethod) else { continue } @@ -791,6 +914,7 @@ extension JavaClassTranslator { return true } } catch { + // FIXME: logging } } @@ -815,114 +939,3 @@ extension [Type?] { } } -extension Type { - /// Adjust the given type to use its bounds, mirroring what we do in - /// mapping Java types into Swift. - func adjustToJavaBounds(adjusted: inout Bool) -> Type { - if let typeVariable = self.as(TypeVariable.self), - typeVariable.getBounds().count == 1, - let bound = typeVariable.getBounds()[0] { - adjusted = true - return bound - } - - if let wildcardType = self.as(WildcardType.self), - wildcardType.getUpperBounds().count == 1, - let bound = wildcardType.getUpperBounds()[0] { - adjusted = true - return bound - } - - return self - } - - /// Determine whether this type is equivalent to or a subtype of the other - /// type. - func isEqualTo(_ other: Type) -> Bool { - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualTo(adjustedOther) - } - - // If both are classes, check for equivalence. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - return selfClass.equals(otherClass.as(JavaObject.self)) - } - - // If both are arrays, check that their component types are equivalent. - if let selfArray = self.as(GenericArrayType.self), - let otherArray = other.as(GenericArrayType.self) { - return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) - } - - // If both are parameterized types, check their raw type and type - // arguments for equivalence. - if let selfParameterizedType = self.as(ParameterizedType.self), - let otherParameterizedType = other.as(ParameterizedType.self) { - if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { - return false - } - - return selfParameterizedType.getActualTypeArguments() - .allTypesEqual(otherParameterizedType.getActualTypeArguments()) - } - - // If both are type variables, compare their bounds. - // FIXME: This is a hack. - if let selfTypeVariable = self.as(TypeVariable.self), - let otherTypeVariable = other.as(TypeVariable.self) { - return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) - } - - // If both are wildcards, compare their upper and lower bounds. - if let selfWildcard = self.as(WildcardType.self), - let otherWildcard = other.as(WildcardType.self) { - return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) - && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) - } - - return false - } - - /// Determine whether this type is equivalent to or a subtype of the - /// other type. - func isEqualToOrSubtypeOf(_ other: Type) -> Bool { - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) - } - - if isEqualTo(other) { - return true - } - - // If both are classes, check for subclassing. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - // If either is a Java array, then this cannot be a subtype relationship - // in Swift. - if selfClass.isArray() || otherClass.isArray() { - return false - } - - return selfClass.isSubclass(of: otherClass) - } - - // Anything object-like is a subclass of java.lang.Object - if let otherClass = other.as(JavaClass.self), - otherClass.getName() == "java.lang.Object" { - if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || - self.is(WildcardType.self) || self.is(TypeVariable.self) { - return true - } - } - return false - } -} diff --git a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift new file mode 100644 index 00000000..7c781962 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaLangReflect +import JavaTypes +import SwiftBasicFormat +import SwiftJava +import SwiftJavaConfigurationShared +import SwiftSyntax +import SwiftSyntaxBuilder + +struct GenericJavaTypeOriginInfo { + enum GenericSource { + /// The source of the generic + case `class`([Type]) + case method + } + + var source: GenericSource + var type: Type +} + +/// if the type (that is used by the Method) is generic, return if the use originates from the method, or a surrounding class. +func getGenericJavaTypeOriginInfo(_ type: Type?, from method: Method) -> [GenericJavaTypeOriginInfo] { + guard let type else { + return [] + } + + guard isGenericJavaType(type) else { + return [] // it's not a generic type, no "origin" of the use to detect + } + + var methodTypeVars = method.getTypeParameters() + + // TODO: also handle nested classes here... + var classTypeVars = method.getDeclaringClass().getTypeParameters() + + var usedTypeVars: [TypeVariable] = [] + + return [] +} + +func isGenericJavaType(_ type: Type?) -> Bool { + guard let type else { + return false + } + + // Check if it's a type variable (e.g., T, E, etc.) + if type.as(TypeVariable.self) != nil { + return true + } + + // Check if it's a parameterized type (e.g., List, Map) + if let paramType = type.as(ParameterizedType.self) { + let typeArgs: [Type?] = paramType.getActualTypeArguments() + + // Check if any of the type arguments are generic + for typeArg in typeArgs { + guard let typeArg else { continue } + if isGenericJavaType(typeArg) { + return true + } + } + } + + // Check if it's a generic array type (e.g., T[], List[]) + if let arrayType = type.as(GenericArrayType.self) { + let componentType = arrayType.getGenericComponentType() + return isGenericJavaType(componentType) + } + + // Check if it's a wildcard type (e.g., ? extends Number, ? super String) + if type.as(WildcardType.self) != nil { + return true + } + + return false +} diff --git a/Sources/SwiftJavaToolLib/JavaHomeSupport.swift b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift new file mode 100644 index 00000000..cd18f7a8 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +/// Detected JAVA_HOME for this process. +package let javaHome: String = findJavaHome() + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +public func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + if let home = getJavaHomeFromLibexecJavaHome(), + !home.isEmpty + { + return home + } + + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" + && ProcessInfo.processInfo.environment["SPI_BUILD"] == nil + { + // Just ignore that we're missing a JAVA_HOME when building in Swift Package Index during general processing where no Java is needed. However, do _not_ suppress the error during SPI's compatibility build stage where Java is required. + return "" + } + 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. +public 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 + } +} diff --git a/Sources/SwiftJavaToolLib/JavaParameterizedType.swift b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift new file mode 100644 index 00000000..a1b2c6b6 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// E.g. `Another` +struct SwiftJavaParameterizedType { + let name: String + let typeArguments: [String] + + init?(name: String?, typeArguments: [String]) { + guard let name else { + return nil + } + + self.name = name + self.typeArguments = typeArguments + } + + func render() -> String { + if typeArguments.isEmpty { + name + } else { + "\(name)<\(typeArguments.joined(separator: ", "))>" + } + } + + +} diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift index 4fc9feb0..655c6765 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift @@ -16,11 +16,6 @@ import Foundation import SwiftJavaConfigurationShared 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) -// } /// Load the configuration file with the given name to populate the known set of /// translated Java classes. @@ -30,10 +25,7 @@ extension JavaTranslator { } for (javaClassName, swiftName) in classes { - translatedClasses[javaClassName] = ( - swiftType: swiftName, - swiftModule: swiftModule - ) + translatedClasses[javaClassName] = SwiftTypeName(module: swiftModule, name: swiftName) } } } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index 9d4d00ca..3a6468d5 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -12,24 +12,45 @@ // //===----------------------------------------------------------------------===// -package extension JavaTranslator { - struct SwiftTypeName: Hashable { - let swiftType: String - let swiftModule: String? +public typealias JavaFullyQualifiedTypeName = String - package init(swiftType: String, swiftModule: String?) { - self.swiftType = swiftType - self.swiftModule = swiftModule +package struct SwiftTypeName: Hashable, CustomStringConvertible { + package let swiftModule: String? + package let swiftType: String + + package init(module: String?, name: String) { + self.swiftModule = module + self.swiftType = name + } + + package var qualifiedName: String { + if let swiftModule { + "\(swiftModule).\(swiftType)" + } else { + "\(swiftType)" + } + } + + package var description: String { + if let swiftModule { + "`\(swiftModule)/\(swiftType)`" + } else { + "`\(swiftType)`" } } +} + +package extension JavaTranslator { struct SwiftToJavaMapping: Equatable { let swiftType: SwiftTypeName - let javaTypes: [String] + let javaTypes: [JavaFullyQualifiedTypeName] - package init(swiftType: SwiftTypeName, javaTypes: [String]) { + package init(swiftType: SwiftTypeName, javaTypes: [JavaFullyQualifiedTypeName]) { self.swiftType = swiftType self.javaTypes = javaTypes + precondition(!javaTypes.contains("com.google.protobuf.AbstractMessage$Builder"), + "\(swiftType) mapped as \(javaTypes)\n\(CommandLine.arguments.joined(separator: " "))") // XXX } } @@ -48,15 +69,23 @@ package extension JavaTranslator { private func mappingDescription(mapping: SwiftToJavaMapping) -> String { let javaTypes = mapping.javaTypes.map { "'\($0)'" }.joined(separator: ", ") - return "Swift Type: '\(mapping.swiftType.swiftModule ?? "")'.'\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" + return "Swift module: '\(mapping.swiftType.swiftModule ?? "")', type: '\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" } } func validateClassConfiguration() throws(ValidationError) { + // for a in translatedClasses { + // print("MAPPING = \(a.key) -> \(a.value.swiftModule?.escapedSwiftName ?? "").\(a.value.swiftType.escapedSwiftName)") + // } + // Group all classes by swift name - let groupedDictionary: [SwiftTypeName: [(String, (String, String?))]] = Dictionary(grouping: translatedClasses, by: { SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) }) + let groupedDictionary: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = Dictionary(grouping: translatedClasses, by: { + // SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) + $0.value + }) // Find all that are mapped to multiple names - let multipleClassesMappedToSameName: [SwiftTypeName: [(String, (String, String?))]] = groupedDictionary.filter { (key: SwiftTypeName, value: [(String, (String, String?))]) in + let multipleClassesMappedToSameName: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = groupedDictionary.filter { + (key: SwiftTypeName, value: [(JavaFullyQualifiedTypeName, SwiftTypeName)]) in value.count > 1 } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 7cb8a7ee..71e01179 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -19,10 +19,16 @@ import SwiftBasicFormat import SwiftSyntax import SwiftJavaConfigurationShared import SwiftSyntaxBuilder +import Foundation +import Logging /// Utility that translates Java classes into Swift source code to access /// those Java classes. package class JavaTranslator { + let config: Configuration + + let log: Logger + /// The name of the Swift module that we are translating into. let swiftModuleName: String @@ -35,7 +41,10 @@ package class JavaTranslator { /// A mapping from the name of each known Java class to the corresponding /// Swift type name and its Swift module. - package var translatedClasses: [String: (swiftType: String, swiftModule: String?)] = [:] + package var translatedClasses: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "byte[]": SwiftTypeName(module: nil, name: "[UInt8]") + ] /// A mapping from the name of each known Java class with the Swift value type /// (and its module) to which it is mapped. @@ -44,8 +53,8 @@ package class JavaTranslator { /// `translatedClasses` should map to a representation of the Java class (i.e., /// an AnyJavaObject-conforming type) whereas the entry here should map to /// a value type. - package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [ - "java.lang.String": ("String", "SwiftJava"), + package let translatedToValueTypes: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.String": SwiftTypeName(module: "SwiftJava", name: "String"), ] /// The set of Swift modules that need to be imported to make the generated @@ -64,15 +73,21 @@ package class JavaTranslator { package var nestedClasses: [String: [JavaClass]] = [:] package init( + config: Configuration, swiftModuleName: String, environment: JNIEnvironment, translateAsClass: Bool = false, format: BasicFormat = JavaTranslator.defaultFormat ) { + self.config = config self.swiftModuleName = swiftModuleName self.environment = environment self.translateAsClass = translateAsClass self.format = format + + var l = Logger(label: "swift-java") + l.logLevel = .init(rawValue: (config.logLevel ?? .info).rawValue)! + self.log = l } /// Clear out any per-file state when we want to start a new file. @@ -112,8 +127,28 @@ extension JavaTranslator { // MARK: Type translation extension JavaTranslator { + + func getSwiftReturnTypeNameAsString( + method: JavaLangReflect.Method, + preferValueTypes: Bool, + outerOptional: OptionalKind + ) throws -> String { + // let returnType = method.getReturnType() + let genericReturnType = method.getGenericReturnType() + + // Special handle the case when the return type is the generic type of the method: ` T foo()` + + // if isGenericJavaType(genericReturnType) { + // print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())") + // getGenericJavaTypeOriginInfo(genericReturnType, from: method) + // } + + return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) + } + /// Turn a Java type into a string. func getSwiftTypeNameAsString( + method: JavaLangReflect.Method? = nil, _ javaType: Type, preferValueTypes: Bool, outerOptional: OptionalKind @@ -123,11 +158,7 @@ extension JavaTranslator { typeVariable.getBounds().count == 1, let bound = typeVariable.getBounds()[0] { - return try getSwiftTypeNameAsString( - bound, - preferValueTypes: preferValueTypes, - outerOptional: outerOptional - ) + return outerOptional.adjustTypeName(typeVariable.getName()) } // Replace wildcards with their upper bound. @@ -164,30 +195,42 @@ extension JavaTranslator { // Handle parameterized types by recursing on the raw type and the type // arguments. - if let parameterizedType = javaType.as(ParameterizedType.self), - let rawJavaType = parameterizedType.getRawType() - { - var rawSwiftType = try getSwiftTypeNameAsString( - rawJavaType, - preferValueTypes: false, - outerOptional: outerOptional - ) + if let parameterizedType = javaType.as(ParameterizedType.self) { + if let rawJavaType = parameterizedType.getRawType() { + var rawSwiftType = try getSwiftTypeNameAsString( + rawJavaType, + preferValueTypes: false, + outerOptional: outerOptional + ) - let optionalSuffix: String - if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { - optionalSuffix = "\(lastChar)" - rawSwiftType.removeLast() - } else { - optionalSuffix = "" - } + let optionalSuffix: String + if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { + optionalSuffix = "\(lastChar)" + rawSwiftType.removeLast() + } else { + optionalSuffix = "" + } - let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in - try typeArg.map { typeArg in - try getSwiftTypeNameAsString(typeArg, preferValueTypes: false, outerOptional: .nonoptional) + let typeArguments: [String] = try parameterizedType.getActualTypeArguments().compactMap { typeArg in + guard let typeArg else { return nil } + + let mappedSwiftName = try getSwiftTypeNameAsString(method: method, typeArg, preferValueTypes: false, outerOptional: .nonoptional) + + // FIXME: improve the get instead... + if mappedSwiftName == nil || mappedSwiftName == "JavaObject" { + // Try to salvage it, is it perhaps a type parameter? + if let method { + if method.getTypeParameters().contains(where: { $0?.getTypeName() == typeArg.getTypeName() }) { + return typeArg.getTypeName() + } + } + } + + return mappedSwiftName } - } - return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + } } // Handle direct references to Java classes. @@ -196,10 +239,12 @@ extension JavaTranslator { } let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes) - var resultString = swiftName - if isOptional { - resultString = outerOptional.adjustTypeName(resultString) - } + let resultString = + if isOptional { + outerOptional.adjustTypeName(swiftName) + } else { + swiftName + } return resultString } @@ -233,7 +278,10 @@ extension JavaTranslator { if preferValueTypes, let translatedValueType = translatedToValueTypes[name] { // Note that we need to import this Swift module. if translatedValueType.swiftModule != swiftModuleName { - importedSwiftModules.insert(translatedValueType.swiftModule) + guard let module = translatedValueType.swiftModule else { + preconditionFailure("Translated value type must have Swift module, but was nil! Type: \(translatedValueType)") + } + importedSwiftModules.insert(module) } return translatedValueType.swiftType diff --git a/Sources/SwiftJavaToolLib/JavaType+Equality.swift b/Sources/SwiftJavaToolLib/JavaType+Equality.swift new file mode 100644 index 00000000..b3d4a37a --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaType+Equality.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava +import JavaLangReflect +import SwiftSyntax +import SwiftJavaConfigurationShared +import Logging + +extension Type { + /// Adjust the given type to use its bounds, mirroring what we do in + /// mapping Java types into Swift. + func adjustToJavaBounds(adjusted: inout Bool) -> Type { + if let typeVariable = self.as(TypeVariable.self), + typeVariable.getBounds().count == 1, + let bound = typeVariable.getBounds()[0] { + adjusted = true + return bound + } + + if let wildcardType = self.as(WildcardType.self), + wildcardType.getUpperBounds().count == 1, + let bound = wildcardType.getUpperBounds()[0] { + adjusted = true + return bound + } + + return self + } + + /// Determine whether this type is equivalent to or a subtype of the other + /// type. + func isEqualTo(_ other: Type, file: String = #file, line: Int = #line, function: String = #function) -> Bool { + if self.javaHolder.object == other.javaHolder.object { + return true + } + + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualTo(adjustedOther) + } + + // If both are classes, check for equivalence. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + return selfClass.equals(otherClass.as(JavaObject.self)) + } + + // If both are arrays, check that their component types are equivalent. + if let selfArray = self.as(GenericArrayType.self), + let otherArray = other.as(GenericArrayType.self) { + return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) + } + + // If both are parameterized types, check their raw type and type + // arguments for equivalence. + if let selfParameterizedType = self.as(ParameterizedType.self), + let otherParameterizedType = other.as(ParameterizedType.self) { + if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { + return false + } + + return selfParameterizedType.getActualTypeArguments() + .allTypesEqual(otherParameterizedType.getActualTypeArguments()) + } + + // If both are type variables, compare their bounds. + // FIXME: This is a hack. + if let selfTypeVariable = self.as(TypeVariable.self), + let otherTypeVariable = other.as(TypeVariable.self) { + return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) + } + + // If both are wildcards, compare their upper and lower bounds. + if let selfWildcard = self.as(WildcardType.self), + let otherWildcard = other.as(WildcardType.self) { + return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) + && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) + } + + return false + } + + /// Determine whether this type is equivalent to or a subtype of the + /// other type. + func isEqualToOrSubtypeOf(_ other: Type) -> Bool { + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) + } + + if isEqualTo(other) { + return true + } + + // If both are classes, check for subclassing. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + // If either is a Java array, then this cannot be a subtype relationship + // in Swift. + if selfClass.isArray() || otherClass.isArray() { + return false + } + + return selfClass.isSubclass(of: otherClass) + } + + // Anything object-like is a subclass of java.lang.Object + if let otherClass = other.as(JavaClass.self), + otherClass.getName() == "java.lang.Object" { + if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || + self.is(WildcardType.self) || self.is(TypeVariable.self) { + return true + } + } + return false + } +} diff --git a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift new file mode 100644 index 00000000..5bb422ea --- /dev/null +++ b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import SwiftJavaConfigurationShared +import Testing + +final class FFMNestedTypesTests { + let class_interfaceFile = + """ + public enum MyNamespace { } + + extension MyNamespace { + public struct MyNestedStruct { + public func test() {} + } + } + """ + + @Test("Import: Nested type in extension MyNamespace { struct MyName {} }") + func test_nested_in_extension() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: class_interfaceFile) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + guard let ty = st.importedTypes["MyNamespace.MyNestedStruct"] else { + fatalError("Didn't import nested type!") + } + + + + } + +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift index 50a0ae26..67d966e3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -134,4 +134,4 @@ struct JNINestedTypesTests { ] ) } -} +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index 61a94062..0833b21f 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -47,7 +47,6 @@ final class JNIUnsignedNumberTests { func jni_unsignedInt_annotate() throws { var config = Configuration() config.unsignedNumbersMode = .annotate - config.logLevel = .trace try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift new file mode 100644 index 00000000..68702b62 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -0,0 +1,174 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import JavaNet +import SwiftJavaShared +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import Foundation + +fileprivate func createTemporaryDirectory(in directory: Foundation.URL) throws -> Foundation.URL { + let uuid = UUID().uuidString + let resolverDirectoryURL = directory.appendingPathComponent("swift-java-testing-\(uuid)") + + try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil) + + return resolverDirectoryURL +} + +/// Returns the directory that should be added to the classpath of the JVM to analyze the sources. +func compileJava(_ sourceText: String) async throws -> Foundation.URL { + let sourceFile = try TempFile.create(suffix: "java", sourceText) + + let classesDirectory = try createTemporaryDirectory(in: FileManager.default.temporaryDirectory) + + let javacProcess = try await _Subprocess.run( + .path(.init("\(javaHome)" + "/bin/javac")), + arguments: [ + "-d", classesDirectory.path, // output directory for .class files + sourceFile.path + ], + output: .string(limit: Int.max, encoding: UTF8.self), + error: .string(limit: Int.max, encoding: UTF8.self) + ) + + // Check if compilation was successful + guard javacProcess.terminationStatus.isSuccess else { + let outString = javacProcess.standardOutput ?? "" + let errString = javacProcess.standardError ?? "" + fatalError("javac '\(sourceFile)' failed (\(javacProcess.terminationStatus));\n" + + "OUT: \(outString)\n" + + "ERROR: \(errString)") + } + + print("Compiled java sources to: \(classesDirectory)") + return classesDirectory +} + +func withJavaTranslator( + javaClassNames: [String], + classpath: [Foundation.URL], + body: (JavaTranslator) throws -> (), + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + print("New withJavaTranslator, for classpath: \(classpath)") + let jvm = try JavaVirtualMachine.shared( + classpath: classpath.map(\.path), + replace: true + ) + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + try body(translator) +} + +/// Translate a Java class and assert that the translated output contains +/// each of the expected "chunks" of text. +func assertWrapJavaOutput( + javaClassNames: [String], + classpath: [Foundation.URL], + assert assertBody: (JavaTranslator) throws -> Void = { _ in }, + expectedChunks: [String], + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + let jvm = try JavaVirtualMachine.shared( + //classpath: classpath.map(\.path), + replace: false + ) + // Do NOT destroy the jvm here, because the JavaClasses will need to deinit, + // and do so while the env is still valid... + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + let classpathJavaURLs = classpath.map({ try! URL.init("\($0)/") }) // we MUST have a trailing slash for JVM to consider it a search directory + let classLoader = URLClassLoader(classpathJavaURLs, environment: environment) + + // FIXME: deduplicate this + translator.startNewFile() + + var swiftCompleteOutputText = "" + + var javaClasses: [JavaClass] = [] + for javaClassName in javaClassNames { + guard let javaClass = try! classLoader.loadClass(javaClassName) else { + fatalError("Could not load Java class '\(javaClassName)' in test \(function) @ \(file):\(line)!") + } + javaClasses.append(javaClass) + + // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!! + // TODO: especially because nested classes + // WrapJavaCommand(). + + let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName + .defaultSwiftNameForJavaClass + translator.translatedClasses[javaClassName] = + .init(module: nil, name: swiftUnqualifiedName) + + try translator.validateClassConfiguration() + + let swiftClassDecls = try translator.translateClass(javaClass) + let importDecls = translator.getImportDecls() + + let swiftFileText = + """ + // --------------------------------------------------------------------------- + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) + \n + """ + swiftCompleteOutputText += swiftFileText + } + + // Run any additional user defined assertions: + try assertBody(translator) + + for expectedChunk in expectedChunks { + // We make the matching in-sensitive to whitespace: + let checkAgainstText = swiftCompleteOutputText.replacing(" ", with: "") + let checkAgainstExpectedChunk = expectedChunk.replacing(" ", with: "") + +let failureMessage = "Expected chunk: \n" + + "\(expectedChunk.yellow)" + + "\n" + + "not found in:\n" + + "\(swiftCompleteOutputText)" + XCTAssertTrue(checkAgainstText.contains(checkAgainstExpectedChunk), + "\(failureMessage)") + } +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index f251766a..e302fdc5 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -14,6 +14,7 @@ @_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared import SwiftJavaToolLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 @@ -48,12 +49,12 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObject { """, """ - @JavaMethod - public func toString() -> String + @JavaMethod + public func toString() -> String """, """ - @JavaMethod - public func wait() throws + @JavaMethod + public func wait() throws """ ] ) @@ -64,7 +65,7 @@ class Java2SwiftTests: XCTestCase { JavaClass.self, swiftTypeName: "MyJavaClass", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -73,8 +74,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaClass { """, """ - @JavaStaticMethod - public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass + @JavaStaticMethod + public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass """, ] ) @@ -100,12 +101,12 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self = APRIL } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -115,19 +116,19 @@ class Java2SwiftTests: XCTestCase { MyArrayList.self, swiftTypeName: "JavaArrayList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.lang.reflect.Array": ("JavaArray", nil), - "java.util.List": ("JavaList", nil), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.lang.reflect.Array": SwiftTypeName(module: nil, name: "JavaArray"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """, """ - @JavaMethod - public func toArray(_ arg0: MyJavaIntFunction?) -> [JavaObject?] + @JavaMethod + public func toArray(_ arg0: MyJavaIntFunction?) -> [T?] """ ] ) @@ -138,13 +139,13 @@ class Java2SwiftTests: XCTestCase { MyLinkedList.self, swiftTypeName: "JavaLinkedList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.util.List": ("JavaList", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """ ] ) @@ -155,9 +156,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.Redirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.Redirect.Type", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect.Type"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -166,8 +167,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.Redirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.Redirect! """, """ extension ProcessBuilder { @@ -183,8 +184,8 @@ class Java2SwiftTests: XCTestCase { public struct Type { """, """ - @JavaMethod - public func type() -> ProcessBuilder.Redirect.`Type`! + @JavaMethod + public func type() -> ProcessBuilder.Redirect.`Type`! """, ] ) @@ -195,9 +196,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.PBRedirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.PBRedirect.JavaType", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect.JavaType"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -206,8 +207,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.PBRedirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.PBRedirect! """, """ extension ProcessBuilder { @@ -223,8 +224,8 @@ class Java2SwiftTests: XCTestCase { public struct JavaType { """, """ - @JavaMethod - public func type() -> ProcessBuilder.PBRedirect.JavaType! + @JavaMethod + public func type() -> ProcessBuilder.PBRedirect.JavaType! """ ] ) @@ -248,9 +249,9 @@ class Java2SwiftTests: XCTestCase { MyObjects.self, swiftTypeName: "MyJavaObjects", translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.Supplier" : ("MySupplier", "JavaUtilFunction"), - "java.lang.String" : ("JavaString", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.Supplier" : SwiftTypeName(module: "JavaUtilFunction", name: "MySupplier"), + "java.lang.String" : SwiftTypeName(module: "SwiftJava", name: "JavaString"), ], expectedChunks: [ """ @@ -261,8 +262,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObjects { """, """ - @JavaStaticMethod - public func requireNonNull(_ arg0: JavaObject?, _ arg1: MySupplier?) -> JavaObject! + @JavaStaticMethod + public func requireNonNull(_ arg0: T?, _ arg1: MySupplier?) -> T """, ] ) @@ -280,20 +281,20 @@ class Java2SwiftTests: XCTestCase { open class JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open func toString() -> String + @JavaMethod + open func toString() -> String """, """ - @JavaMethod - open func wait() throws + @JavaMethod + open func wait() throws """, """ - @JavaMethod - open func clone() throws -> JavaObject! + @JavaMethod + open func clone() throws -> JavaObject! """, ] ) @@ -305,7 +306,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaString", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -314,24 +315,24 @@ class Java2SwiftTests: XCTestCase { open class JavaString: JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open override func toString() -> String + @JavaMethod + open override func toString() -> String """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, """ - @JavaMethod - open func intern() -> String + @JavaMethod + open func intern() -> String """, """ - @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> String + @JavaStaticMethod + public func valueOf(_ arg0: Int64) -> String """, ] ) @@ -361,12 +362,12 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self.init(javaHolder: APRIL.javaHolder) } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -380,9 +381,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.ClassLoader" : ("ClassLoader", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.ClassLoader" : SwiftTypeName(module: "SwiftJava", name: "ClassLoader"), + "java.net.URL" : SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -391,12 +392,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: ClassLoader { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open override func findResource(_ arg0: String) -> URL! + @JavaMethod + open override func findResource(_ arg0: String) -> URL! """, ] ) @@ -411,8 +412,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.net.URL": SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -421,12 +422,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: JavaObject { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open func findResource(_ arg0: String) -> URL! + @JavaMethod + open func findResource(_ arg0: String) -> URL! """, ] ) @@ -440,9 +441,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaByte", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Number" : ("JavaNumber", "SwiftJava"), - "java.lang.Byte" : ("JavaByte", "SwiftJava"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Number": SwiftTypeName(module: "SwiftJava", name: "JavaNumber"), + "java.lang.Byte": SwiftTypeName(module: "SwiftJava", name: "JavaByte"), ], expectedChunks: [ "import SwiftJava", @@ -451,8 +452,8 @@ class Java2SwiftTests: XCTestCase { open class JavaByte: JavaNumber { """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, ] ) @@ -464,8 +465,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "MyJavaIntFunction", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ "import SwiftJava", @@ -474,8 +475,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaIntFunction { """, """ - @JavaMethod - public func apply(_ arg0: Int32) -> JavaObject! + @JavaMethod + public func apply(_ arg0: Int32) -> R! """, ] ) @@ -487,11 +488,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Method", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -500,16 +501,16 @@ class Java2SwiftTests: XCTestCase { open class Method: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -521,11 +522,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Constructor", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -534,16 +535,16 @@ class Java2SwiftTests: XCTestCase { open class Constructor: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable>?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable>?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -555,10 +556,10 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "NIOByteBuffer", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.nio.Buffer": ("NIOBuffer", "JavaNio"), - "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaNio"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class": SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.nio.Buffer": SwiftTypeName(module: "JavaNio", name: "NIOBuffer"), + "java.nio.ByteBuffer": SwiftTypeName(module: "JavaNio", name: "NIOByteBuffer"), ], expectedChunks: [ "import JavaNio", @@ -580,61 +581,58 @@ class Java2SwiftTests: XCTestCase { } @JavaClass("java.lang.ClassLoader") -public struct ClassLoader { } +fileprivate struct ClassLoader { } @JavaClass("java.security.SecureClassLoader") -public struct SecureClassLoader { } +fileprivate struct SecureClassLoader { } @JavaClass("java.net.URLClassLoader") -public struct URLClassLoader { } - +fileprivate struct URLClassLoader { } @JavaClass("java.util.ArrayList") -public struct MyArrayList { +fileprivate struct MyArrayList { } @JavaClass("java.util.LinkedList") -public struct MyLinkedList { +fileprivate struct MyLinkedList { } @JavaClass("java.lang.String") -public struct MyJavaString { +fileprivate struct MyJavaString { } @JavaClass("java.util.Objects") -public struct MyObjects { } +fileprivate struct MyObjects { } @JavaInterface("java.util.function.Supplier") -public struct MySupplier { } +fileprivate struct MySupplier { } @JavaInterface("java.util.function.IntFunction") -public struct MyJavaIntFunction { +fileprivate struct MyJavaIntFunction { } @JavaClass("java.lang.reflect.Method", extends: Executable.self) -public struct Method { +fileprivate struct Method { } @JavaClass("java.lang.reflect.Constructor", extends: Executable.self) -public struct Constructor { +fileprivate struct Constructor { } @JavaClass("java.lang.reflect.Executable") -public struct Executable { +fileprivate struct Executable { } @JavaInterface("java.lang.reflect.TypeVariable") -public struct TypeVariable { +fileprivate struct TypeVariable { } @JavaClass("java.nio.Buffer") -open class NIOBuffer: JavaObject { - +fileprivate class NIOBuffer: JavaObject { } @JavaClass("java.nio.ByteBuffer") -open class NIOByteBuffer: NIOBuffer { - +fileprivate class NIOByteBuffer: NIOBuffer { } /// Translate a Java class and assert that the translated output contains @@ -643,9 +641,8 @@ func assertTranslatedClass( _ javaType: JavaClassType.Type, swiftTypeName: String, asClass: Bool = false, - translatedClasses: [ - String: (swiftType: String, swiftModule: String?) - ] = [:], + config: Configuration = Configuration(), + translatedClasses: [String: SwiftTypeName] = [:], nestedClasses: [String: [JavaClass]] = [:], expectedChunks: [String], file: StaticString = #filePath, @@ -653,13 +650,14 @@ func assertTranslatedClass( ) throws { let environment = try jvm.environment() let translator = JavaTranslator( + config: config, swiftModuleName: "SwiftModule", environment: environment, translateAsClass: asClass ) translator.translatedClasses = translatedClasses - translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil) + translator.translatedClasses[javaType.fullJavaClassName] = SwiftTypeName(module: nil, name: swiftTypeName) translator.nestedClasses = nestedClasses translator.startNewFile() @@ -677,12 +675,22 @@ func assertTranslatedClass( \(translatedDecls.map { $0.description }.joined(separator: "\n")) """ + func normalizeWhitespace(_ text: String) -> String { + return text.components(separatedBy: .newlines) + .map { $0.trimmingCharacters(in: .whitespaces) } + .joined(separator: "\n") + } + + let normalizedSwiftFileText = normalizeWhitespace(swiftFileText) + for expectedChunk in expectedChunks { - if swiftFileText.contains(expectedChunk) { + let normalizedExpectedChunk = normalizeWhitespace(expectedChunk) + + if normalizedSwiftFileText.contains(normalizedExpectedChunk) { continue } - XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line) + XCTFail("Expected chunk:\n---\n\(expectedChunk.yellow)\n---\nnot found in:\n===\n\(swiftFileText)\n===", file: file, line: line) } } } diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift new file mode 100644 index 00000000..9983813d --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaUtilJar +@_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared +import SwiftJavaShared +import SwiftJavaToolLib +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import _Subprocess + +class JavaTranslatorTests: XCTestCase { + + func translateGenericMethodParameters() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair( + String name, + Item key, + Item value + ) { return null; } + } + """) + + try withJavaTranslator( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass", + ], + classpath: [classpathURL], + ) { translator in + + } + } +} diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift index 220f1c61..558add20 100644 --- a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift @@ -14,15 +14,16 @@ import SwiftJavaToolLib import XCTest +import SwiftJavaConfigurationShared final class JavaTranslatorValidationTests: XCTestCase { func testValidationError() throws { - let translator = try JavaTranslator(swiftModuleName: "SwiftModule", environment: jvm.environment()) + let translator = try JavaTranslator(config: Configuration(), swiftModuleName: "SwiftModule", environment: jvm.environment()) translator.translatedClasses = [ - "TestClass": ("Class1", "Module1"), - "TestClass2": ("Class1", "Module2"), - "TestClass3": ("Class1", "Module1"), - "TestClass4": ("Class1", nil) + "TestClass": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass2": SwiftTypeName(module: "Module2", name: "Class1"), + "TestClass3": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass4": SwiftTypeName(module: nil, name: "Class1") ] XCTAssertThrowsError(try translator.validateClassConfiguration()) { error in @@ -31,7 +32,7 @@ final class JavaTranslatorValidationTests: XCTestCase { switch validationError { case .multipleClassesMappedToSameName(let swiftToJavaMapping): XCTAssertEqual(swiftToJavaMapping, [ - JavaTranslator.SwiftToJavaMapping(swiftType: .init(swiftType: "Class1", swiftModule: "Module1"), + JavaTranslator.SwiftToJavaMapping(swiftType: .init(module: "Module1", name: "Class1"), javaTypes: ["TestClass", "TestClass3"]) ]) } diff --git a/Tests/SwiftJavaToolLibTests/TempFileTools.swift b/Tests/SwiftJavaToolLibTests/TempFileTools.swift new file mode 100644 index 00000000..5b133108 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/TempFileTools.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 Foundation + +/// Example demonstrating how to create a temporary file using Swift Foundation APIs +public class TempFile { + + public static func create( + suffix: String, + _ contents: String = "", + in tempDirectory: URL = FileManager.default.temporaryDirectory) throws -> URL { + let tempFileName = "tmp_\(UUID().uuidString).\(suffix)" + let tempFileURL = tempDirectory.appendingPathComponent(tempFileName) + + try contents.write(to: tempFileURL, atomically: true, encoding: .utf8) + + return tempFileURL + } + public static func delete(at fileURL: URL) throws { + try FileManager.default.removeItem(at: fileURL) + } +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift new file mode 100644 index 00000000..1c3d10d2 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -0,0 +1,273 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import SwiftJavaShared +import JavaNet +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 + +final class WrapJavaTests: XCTestCase { + + func testWrapJavaFromCompiledJavaSource() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class ExampleSimpleClass {} + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """ + ] + ) + } + + // @Test + func testWrapJavaGenericMethod_singleGeneric() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + KeyType getGeneric(Item key) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getGeneric(_ arg0: Item?) -> KeyType + """, + ] + ) + } + + // This is just a warning in Java, but a hard error in Swift, so we must 'prune' generic params + func testWrapJavaGenericMethod_pruneNotUsedGenericParam() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + final class ExampleSimpleClass { + // use in return type + KeyType getGeneric() { + return null; + } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getGeneric() -> KeyType + """, + ] + ) + } + + func testWrapJavaGenericMethod_multipleGenerics() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair(String name, Item key, Item value) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Item") + open class Item: JavaObject { + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getPair(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> Pair! + """, + ] + ) + } + + func test_Java2Swift_returnType_generic() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + final class List {} + final class Map {} + + class GenericClass { + public T getClassGeneric() { return null; } + + public M getMethodGeneric() { return null; } + + public Map getMixedGeneric() { return null; } + + public String getNonGeneric() { return null; } + + public List getParameterizedClassGeneric() { return null; } + + public List getWildcard() { return null; } + + public T[] getGenericArray() { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.GenericClass", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getClassGeneric() -> T + """, + """ + @JavaMethod + open func getNonGeneric() -> String + """, + ] + ) + } + + func testGenericSuperclass() async throws { + return // FIXME: we need this + + let classpathURL = try await compileJava( + """ + package com.example; + + class ByteArray {} + class CompressingStore extends AbstractStore {} + abstract class AbstractStore {} // implements Store {} + // interface Store {} + + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ByteArray", + // TODO: what if we visit in other order, does the wrap-java handle it + // "com.example.Store", + "com.example.AbstractStore", + "com.example.CompressingStore", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ByteArray") + open class ByteArray: JavaObject { + """, + // """ + // @JavaInterface("com.example.Store") + // public struct Store { + // """, + """ + @JavaClass("com.example.CompressingStore") + open class CompressingStore: AbstractStore { + """ + ] + ) + } +}