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 + } + } + """, + ] + ) + } }