From 258ccfff6971b872941ed1510c36fc40682880a8 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 8 Oct 2024 16:17:54 +0900 Subject: [PATCH 1/5] Prepare accessor function descriptors for properties: get set --- .../ExampleSwiftLibrary/MySwiftLibrary.swift | 2 + Sources/JExtractSwift/ImportedDecls.swift | 159 +++++++++++++++++- .../Swift2JavaTranslator+Printing.swift | 83 ++++++++- Sources/JExtractSwift/Swift2JavaVisitor.swift | 64 +++++-- Sources/JExtractSwift/TranslatedType.swift | 11 ++ .../FunctionDescriptorImportTests.swift | 125 +++++++++++--- 6 files changed, 397 insertions(+), 47 deletions(-) diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index e5faaad7..4928233e 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -55,6 +55,8 @@ public class MySwiftClass { p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))") } + public var counter: Int32 = 0 + public func voidMethod() { p("") } diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index c8cdd8fa..84314bc5 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -16,6 +16,7 @@ import Foundation import JavaTypes import SwiftSyntax +/// Any imported (Swift) declaration protocol ImportedDecl { } @@ -32,6 +33,7 @@ public struct ImportedNominalType: ImportedDecl { public var initializers: [ImportedFunc] = [] public var methods: [ImportedFunc] = [] + public var variables: [ImportedVariable] = [] public init(swiftTypeName: String, javaType: JavaType, swiftMangledName: String? = nil, kind: NominalTypeKind) { self.swiftTypeName = swiftTypeName @@ -195,7 +197,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { public var swiftMangledName: String = "" - public var swiftDeclRaw: String? = nil + public var syntax: String? = nil public var isInit: Bool = false @@ -221,7 +223,160 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { Swift mangled name: Imported from: - \(swiftDeclRaw ?? "") + \(syntax?.description ?? "") + } + """ + } +} + +public enum VariableAccessorKind { + case get + case set + + public var renderDescFieldName: String { + switch self { + case .get: "DESC_GET" + case .set: "DESC_SET" + } + } +} + +public struct ImportedVariable: ImportedDecl, CustomStringConvertible { + /// 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: Set = [.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)" + var funcDecl = ImportedFunc( + parentName: self.parentName, + identifier: self.identifier, + returnType: TranslatedType.void, + parameters: [.init(param: newValueParam, type: self.returnType)]) + funcDecl.swiftMangledName = self.swiftMangledName + "s" // form mangled name of the getter by adding the suffix + return funcDecl + + case .get: + var funcDecl = ImportedFunc( + parentName: self.parentName, + identifier: self.identifier, + returnType: self.returnType, + parameters: []) + funcDecl.swiftMangledName = self.swiftMangledName + "g" // form mangled name of the getter by adding the suffix + return funcDecl + } + } + + public func effectiveAccessorParameters(_ kind: VariableAccessorKind, selfVariant: SelfParameterVariant?) -> [ImportedParam] { + var params: [ImportedParam] = [] + + if kind == .set { + let newValueParam: FunctionParameterSyntax = "_ newValue: \(raw: self.returnType.swiftTypeName)" + params.append( + ImportedParam( + param: 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 selfVariant { + case nil, .wrapper: + break + + case .pointer: + let selfParam: FunctionParameterSyntax = "self$: $swift_pointer" + params.append( + ImportedParam( + param: selfParam, + type: parentName + ) + ) + + case .memorySegment: + let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment" + var parentForSelf = parentName + parentForSelf.javaType = .javaForeignMemorySegment + params.append( + ImportedParam( + param: selfParam, + type: parentForSelf + ) + ) + } + } + + return params + } + + public var swiftMangledName: String = "" + + public var syntax: VariableDeclSyntax? = nil + + public init( + parentName: TranslatedType?, + identifier: String, + returnType: TranslatedType + ) { + 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 ?? "") } """ } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 35ed6133..14b44e03 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -437,7 +437,7 @@ extension Swift2JavaTranslator { * Create an instance of {@code \(parentName.unqualifiedJavaTypeName)}. * * {@snippet lang=swift : - * \(decl.swiftDeclRaw ?? "") + * \(decl.syntax ?? "") * } */ public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) { @@ -470,7 +470,7 @@ extension Swift2JavaTranslator { /** * Function descriptor for: * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.swiftDeclRaw ?? "") + * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") * } */ public static FunctionDescriptor \(decl.baseIdentifier)$descriptor() { @@ -484,7 +484,7 @@ extension Swift2JavaTranslator { /** * Downcall method handle for: * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.swiftDeclRaw ?? "") + * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") * } */ public static MethodHandle \(decl.baseIdentifier)$handle() { @@ -498,7 +498,7 @@ extension Swift2JavaTranslator { /** * Address for: * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.swiftDeclRaw ?? "") + * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") * } */ public static MemorySegment \(decl.baseIdentifier)$address() { @@ -557,7 +557,7 @@ extension Swift2JavaTranslator { """ /** * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.swiftDeclRaw ?? "") + * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") * } */ public \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) { @@ -572,7 +572,60 @@ extension Swift2JavaTranslator { """ /** * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.swiftDeclRaw ?? "") + * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") + * } + */ + public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: selfVariant))) { + var mh$ = \(decl.baseIdentifier).HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(\(renderForwardParams(decl, selfVariant: .memorySegment))); + } + \(maybeReturnCast) mh$.invokeExact(\(renderForwardParams(decl, selfVariant: selfVariant))); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ) + } + + public func printPropertyAccessorDowncallMethod( + _ printer: inout CodePrinter, + decl: ImportedFunc, + selfVariant: 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 selfVariant == 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, selfVariant: .wrapper))) { + \(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardParams(decl, selfVariant: .wrapper))); + } + """ + ) + return + } + + printer.print( + """ + /** + * {@snippet lang=swift : + * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") * } */ public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: selfVariant))) { @@ -656,8 +709,11 @@ extension Swift2JavaTranslator { return ps.joined(separator: ", ") } - public func printFunctionDescriptorValue(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - printer.start("public static final FunctionDescriptor DESC = ") + public func printFunctionDescriptorValue( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + fieldName: String = "DESC") { + printer.start("public static final FunctionDescriptor \(fieldName) = ") let parameterLayoutDescriptors = javaMemoryLayoutDescriptors( forParametersOf: decl, @@ -692,4 +748,15 @@ extension Swift2JavaTranslator { printer.outdent(); printer.print(");"); } + + public func printPropertyAccessorDescriptorValue( + _ printer: inout CodePrinter, + _ decl: ImportedVariable, + _ kind: VariableAccessorKind) { + guard let funcDecl = decl.accessorFunc(kind: kind) else { + return + } + + printFunctionDescriptorValue(&printer, funcDecl, fieldName: kind.renderDescFieldName) + } } diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 3c8c252d..db42c02b 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -80,7 +80,6 @@ final class Swift2JavaVisitor: SyntaxVisitor { self.log.debug("Import function: \(node.kind) \(node.name)") - // TODO: this must handle inout and other stuff, strip it off etc let returnTy: TypeSyntax if let returnClause = node.signature.returnClause { returnTy = returnClause.type @@ -106,14 +105,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - let argumentLabels = node.signature.parameterClause.parameters.map { param in - param.firstName.identifier?.name ?? "_" - } - let argumentLabelsStr = String(argumentLabels.flatMap { label in - label + ":" - }) - - let fullName = "\(node.name.text)(\(argumentLabelsStr))" + let fullName = "\(node.name.text)" var funcDecl = ImportedFunc( parentName: currentTypeName.map { translator.importedTypes[$0] }??.translatedType, @@ -121,7 +113,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { returnType: javaResultType, parameters: params ) - funcDecl.swiftDeclRaw = "\(node.trimmed)" // TODO: rethink this, it's useful for comments in Java + funcDecl.syntax = "\(node.trimmed)" // TODO: rethink this, it's useful for comments in Java // Retrieve the mangled name, if available. if let mangledName = node.mangledNameFromComment { @@ -138,6 +130,56 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } + override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + self.log.warning("NODE: \(node.debugDescription)") + + guard let binding = node.bindings.first else { + return .skipChildren + } + + let fullName = "\(binding.pattern.trimmed)" + + // TODO filter out kinds of variables we cannot import + + self.log.info("Import variable: \(node.kind) \(fullName)") + + let returnTy: TypeSyntax + if let typeAnnotation = binding.typeAnnotation{ + returnTy = typeAnnotation.type + } else { + returnTy = "Swift.Void" + } + + let javaResultType: TranslatedType + do { + javaResultType = try cCompatibleType(for: returnTy) + } catch { + self.log.info("Unable to import variable \(node.debugDescription) - \(error)") + return .skipChildren + } + + var varDecl = ImportedVariable( + 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.info("Record variable in \(currentTypeName)") + translator.importedTypes[currentTypeName]!.variables.append(varDecl) + } else { + fatalError("Global variables are not supported yet: \(node.debugDescription)") + } + + return .skipChildren + } + override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard let currentTypeName, let currentType = translator.importedTypes[currentTypeName] else { @@ -173,7 +215,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { parameters: params ) funcDecl.isInit = true - funcDecl.swiftDeclRaw = "\(node.trimmed)" // TODO: rethink this, it's useful for comments in Java + funcDecl.syntax = "\(node.trimmed)" // TODO: rethink this, it's useful for comments in Java // Retrieve the mangled name, if available. if let mangledName = node.mangledNameFromComment { diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index c759b3af..367c92cc 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -234,6 +234,17 @@ public struct TranslatedType { } } +extension TranslatedType { + public static var void: Self { + TranslatedType( + cCompatibleConvention: .direct, + originalSwiftType: "Void", + cCompatibleSwiftType: "Swift.Void", + cCompatibleJavaMemoryLayout: .primitive(.void), + javaType: JavaType.void) + } +} + /// Describes the C-compatible layout as it should be referenced from Java. enum CCompatibleJavaMemoryLayout { /// A primitive Java type that has a direct counterpart in C. diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index cf175197..25152c76 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -45,34 +45,13 @@ final class FunctionDescriptorTests { // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC3len3capACSi_SitcfC public init(len: Swift.Int, cap: Swift.Int) @objc deinit - } - """ - - func functionDescriptorTest( - _ methodIdentifier: String, - javaPackage: String = "com.example.swift", - swiftModuleName: String = "SwiftModule", - logLevel: Logger.Level = .warning, - body: (String) async throws -> () - ) async throws { - let st = Swift2JavaTranslator( - javaPackage: javaPackage, - swiftModuleName: swiftModuleName - ) - st.log.logLevel = logLevel - - try await st.analyze(swiftInterfacePath: "/fake/Sample.swiftinterface", text: interfaceFile) - - let funcDecl = st.importedGlobalFuncs.first { - $0.baseIdentifier == methodIdentifier - }! - let output = CodePrinter.toString { printer in - st.printFunctionDescriptorValue(&printer, funcDecl) + // #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 } - - try await body(output) - } + """ @Test func FunctionDescriptor_globalTakeInt() async throws { @@ -121,4 +100,98 @@ final class FunctionDescriptorTests { } } + @Test + func FunctionDescriptor_class_counter_get() async throws { + try await variableAccessorDescriptorTest("counter", .get) { output in + assertOutput( + output, + expected: + """ + public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( + /* -> */SWIFT_INT32, + SWIFT_POINTER + ); + """ + ) + } + } + @Test + func FunctionDescriptor_class_counter_set() async throws { + try await variableAccessorDescriptorTest("counter", .set) { output in + assertOutput( + output, + expected: + """ + public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( + SWIFT_INT32, + SWIFT_POINTER + ); + """ + ) + } + } + } + +extension FunctionDescriptorTests { + + func functionDescriptorTest( + _ methodIdentifier: String, + javaPackage: String = "com.example.swift", + swiftModuleName: String = "SwiftModule", + logLevel: Logger.Level = .trace, + body: (String) async throws -> () + ) async throws { + let st = Swift2JavaTranslator( + javaPackage: javaPackage, + swiftModuleName: swiftModuleName + ) + st.log.logLevel = logLevel + + try await st.analyze(swiftInterfacePath: "/fake/Sample.swiftinterface", text: interfaceFile) + + let funcDecl = st.importedGlobalFuncs.first { + $0.baseIdentifier == methodIdentifier + }! + + let output = CodePrinter.toString { printer in + st.printFunctionDescriptorValue(&printer, funcDecl) + } + + try await body(output) + } + + func variableAccessorDescriptorTest( + _ methodIdentifier: String, + _ kind: VariableAccessorKind, + javaPackage: String = "com.example.swift", + swiftModuleName: String = "SwiftModule", + logLevel: Logger.Level = .trace, + body: (String) async throws -> () + ) async throws { + let st = Swift2JavaTranslator( + javaPackage: javaPackage, + swiftModuleName: swiftModuleName + ) + st.log.logLevel = logLevel + + try await st.analyze(swiftInterfacePath: "/fake/Sample.swiftinterface", text: interfaceFile) + + let varDecl: ImportedVariable? = + st.importedTypes.values.compactMap { + $0.variables.first { + $0.identifier == methodIdentifier + } + }.first + guard let varDecl else { + fatalError("Cannot find descriptor of: \(methodIdentifier)") as! ImportedVariable + } + + let getOutput = CodePrinter.toString { printer in + st.printPropertyAccessorDescriptorValue(&printer, varDecl, kind) + } + + try await body(getOutput) + } + +} \ No newline at end of file From a8f1cc996f713f941a2e6e58e1784bc1f617ace3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 8 Oct 2024 17:48:00 +0900 Subject: [PATCH 2/5] Printing for handles, descriptors and address methods for props --- Package.swift | 4 +- .../ImportedDecls+Printing.swift | 65 +++++++++ Sources/JExtractSwift/ImportedDecls.swift | 10 +- .../Swift2JavaTranslator+Printing.swift | 94 ++++++++++--- .../Asserts/TextAssertions.swift | 19 ++- .../FunctionDescriptorImportTests.swift | 2 +- .../VariableImportTests.swift | 123 ++++++++++++++++++ 7 files changed, 280 insertions(+), 37 deletions(-) create mode 100644 Sources/JExtractSwift/ImportedDecls+Printing.swift create mode 100644 Tests/JExtractSwiftTests/VariableImportTests.swift diff --git a/Package.swift b/Package.swift index df96b8f4..4ec487b7 100644 --- a/Package.swift +++ b/Package.swift @@ -122,7 +122,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), - .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), // TODO: remove, we should not need 'nm' or process callouts + .package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.1.0")), ], targets: [ .macro( @@ -282,6 +283,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: "Collections", package: "swift-collections"), "_Subprocess", "JavaTypes", ], diff --git a/Sources/JExtractSwift/ImportedDecls+Printing.swift b/Sources/JExtractSwift/ImportedDecls+Printing.swift new file mode 100644 index 00000000..8eaa4f66 --- /dev/null +++ b/Sources/JExtractSwift/ImportedDecls+Printing.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// 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 +import OrderedCollections + +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 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" + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index 84314bc5..d89ed77d 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -15,6 +15,7 @@ import Foundation import JavaTypes import SwiftSyntax +import OrderedCollections /// Any imported (Swift) declaration protocol ImportedDecl { @@ -232,13 +233,6 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { public enum VariableAccessorKind { case get case set - - public var renderDescFieldName: String { - switch self { - case .get: "DESC_GET" - case .set: "DESC_SET" - } - } } public struct ImportedVariable: ImportedDecl, CustomStringConvertible { @@ -257,7 +251,7 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible { /// 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: Set = [.get, .set] + public var supportedAccessorKinds: OrderedSet = [.get, .set] /// This is the base identifier for the function, e.g., "init" for an /// initializer or "f" for "f(a:b:)". diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 14b44e03..54897e5a 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -465,54 +465,108 @@ extension Swift2JavaTranslator { 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, selfVariant: .memorySegment) + printFuncDowncallMethod(&printer, decl: decl, selfVariant: .wrapper) + } else { + printFuncDowncallMethod(&printer, decl: decl, selfVariant: nil) + } + } + + private func printFunctionAddressMethod(_ printer: inout CodePrinter, + decl: ImportedFunc, + accessorKind: VariableAccessorKind? = nil) { + + let addrName = accessorKind?.renderAddrFieldName ?? "ADDR" + let methodNameSegment = accessorKind?.renderMethodNameSegment ?? "" + let snippet = decl.renderCommentSnippet ?? "* " + printer.print( """ /** - * Function descriptor for: - * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") - * } + * Address for: + \(snippet) */ - public static FunctionDescriptor \(decl.baseIdentifier)$descriptor() { - return \(decl.baseIdentifier).DESC; + 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 ?? "HANDLE" + let methodNameSegment = accessorKind?.renderMethodNameSegment ?? "" + let snippet = decl.renderCommentSnippet ?? "* " printer.print( """ /** * Downcall method handle for: - * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") - * } + \(snippet) */ - public static MethodHandle \(decl.baseIdentifier)$handle() { - return \(decl.baseIdentifier).HANDLE; + 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 ?? "DESC" + let methodNameSegment = accessorKind?.renderMethodNameSegment ?? "" + let snippet = decl.renderCommentSnippet ?? "* " printer.print( """ /** - * Address for: - * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") - * } + * Function descriptor for: + \(snippet) */ - public static MemorySegment \(decl.baseIdentifier)$address() { - return \(decl.baseIdentifier).ADDR; + 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 { + printPropertyAccessorDescriptorValue(&printer, decl, accessorKind) +// printFunctionDescriptorValue(&printer, decl); +// printFindMemorySegmentAddrByMangledName(&printer, decl) +// printMethodDowncallHandleForAddrDesc(&printer) + } + } + + 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) + } // Render the basic "make the downcall" function if decl.hasParent { - printFuncDowncallMethod(&printer, decl: decl, selfVariant: .memorySegment) - printFuncDowncallMethod(&printer, decl: decl, selfVariant: .wrapper) +// printFuncDowncallMethod(&printer, decl: decl, selfVariant: .memorySegment) +// printFuncDowncallMethod(&printer, decl: decl, selfVariant: .wrapper) } else { - printFuncDowncallMethod(&printer, decl: decl, selfVariant: nil) +// printFuncDowncallMethod(&printer, decl: decl, selfVariant: nil) } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 8017822f..84c4b2ec 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -17,6 +17,7 @@ import Testing import struct Foundation.CharacterSet func assertOutput( + dump: Bool = false, _ got: String, expected: String, fileID: String = #fileID, @@ -38,7 +39,7 @@ func assertOutput( let ge = g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let ee = e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - if ge != ee { + if ge.commonPrefix(with: ee) != ee { // print("") // print("[\(file):\(line)] " + "Difference found on line: \(no + 1)!".red) // print("Expected @ \(file):\(Int(line) + no + 3 /*formatting*/ + 1):") @@ -55,15 +56,19 @@ func assertOutput( } - if diffLineNumbers.count > 0 { + let hasDiff = diffLineNumbers.count > 0 + if hasDiff || dump{ print("") - print("error: Number of not matching lines: \(diffLineNumbers.count)!".red) + if hasDiff { + print("error: Number of not matching lines: \(diffLineNumbers.count)!".red) - print("==== ---------------------------------------------------------------") - print("Expected output:") - for (n, e) in expectedLines.enumerated() { - print("\(e)".yellow(if: diffLineNumbers.contains(n))) + print("==== ---------------------------------------------------------------") + print("Expected output:") + for (n, e) in expectedLines.enumerated() { + print("\(e)".yellow(if: diffLineNumbers.contains(n))) + } } + print("==== ---------------------------------------------------------------") print("Got output:") for (n, g) in gotLines.enumerated() { diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 25152c76..f52757a4 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -184,7 +184,7 @@ extension FunctionDescriptorTests { } }.first guard let varDecl else { - fatalError("Cannot find descriptor of: \(methodIdentifier)") as! ImportedVariable + fatalError("Cannot find descriptor of: \(methodIdentifier)") } let getOutput = CodePrinter.toString { printer in diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift new file mode 100644 index 00000000..5674780e --- /dev/null +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -0,0 +1,123 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwift +import Testing + +final class VariableImportTests { + let class_interfaceFile = + """ + // swift-interface-format-version: 1.0 + // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.7.6 clang-1600.0.24.1) + // swift-module-flags: -target arm64-apple-macosx15.0 -enable-objc-interop -enable-library-evolution -module-name MySwiftLibrary + import Darwin.C + import Darwin + import Swift + import _Concurrency + import _StringProcessing + import _SwiftConcurrencyShims + + public class MySwiftClass { + public var counterInt: Int + } + """ + + @Test("Import: var counter: Int") + func variable_() async throws { + let st = Swift2JavaTranslator( + javaPackage: "com.example.swift", + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .error + + try await st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile) + + let identifier = "counterInt" + let varDecl: ImportedVariable? = + st.importedTypes.values.compactMap { + $0.variables.first { + $0.identifier == identifier + } + }.first + guard let varDecl else { + fatalError("Cannot find: \(identifier)") + } + + let output = CodePrinter.toString { printer in + st.printVariableDowncallMethods(&printer, varDecl) + } + + assertOutput( + dump: true, + output, + expected: + """ + // ==== -------------------------------------------------- + // counterInt + private static class counterInt { + public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( + /* -> */SWIFT_INT, + SWIFT_POINTER + ); + public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( + SWIFT_INT, + SWIFT_POINTER + ); + } + /** + * Function descriptor for: + * + */ + public static FunctionDescriptor counterInt$get$descriptor() { + return counterInt.DESC_GET; + } + /** + * Downcall method handle for: + * + */ + public static MethodHandle counterInt$get$handle() { + return counterInt.HANDLE_GET; + } + /** + * Address for: + * + */ + public static MemorySegment counterInt$get$address() { + return counterInt.ADDR_GET; + } + /** + * Function descriptor for: + * + */ + public static FunctionDescriptor counterInt$set$descriptor() { + return counterInt.DESC_SET; + } + /** + * Downcall method handle for: + * + */ + public static MethodHandle counterInt$set$handle() { + return counterInt.HANDLE_SET; + } + /** + * Address for: + * + */ + public static MemorySegment counterInt$set$address() { + return counterInt.ADDR_SET; + } + """ + ) + } +} \ No newline at end of file From aa2d0179cf27fe305bc04a3d093c7c8f3dde9d82 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 8 Oct 2024 19:02:20 +0900 Subject: [PATCH 3/5] getter and setter generated for stored property --- .../Convenience/String+Extensions.swift | 25 ++++++++ .../ImportedDecls+Printing.swift | 29 +++++++++ .../Swift2JavaTranslator+Printing.swift | 63 ++++++++++++------- .../JExtractSwiftTests/FuncImportTests.swift | 10 +-- .../VariableImportTests.swift | 44 +++++++++++++ 5 files changed, 142 insertions(+), 29 deletions(-) create mode 100644 Sources/JExtractSwift/Convenience/String+Extensions.swift diff --git a/Sources/JExtractSwift/Convenience/String+Extensions.swift b/Sources/JExtractSwift/Convenience/String+Extensions.swift new file mode 100644 index 00000000..53781cef --- /dev/null +++ b/Sources/JExtractSwift/Convenience/String+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 +// +//===----------------------------------------------------------------------===// + +extension String { + + // TODO: naive implementation good enough for our simple case `methodMethodSomething` -> `MethodSomething` + var toCamelCase: String { + guard let f = first else { + return self + } + + return "\(f.uppercased())\(String(dropFirst()))" + } +} \ No newline at end of file diff --git a/Sources/JExtractSwift/ImportedDecls+Printing.swift b/Sources/JExtractSwift/ImportedDecls+Printing.swift index 8eaa4f66..84f718e0 100644 --- a/Sources/JExtractSwift/ImportedDecls+Printing.swift +++ b/Sources/JExtractSwift/ImportedDecls+Printing.swift @@ -62,4 +62,33 @@ extension VariableAccessorKind { 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 + } } \ No newline at end of file diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 54897e5a..3f021d07 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -102,7 +102,7 @@ extension Swift2JavaTranslator { printModuleClass(&printer) { printer in // TODO: print all "static" methods for decl in importedGlobalFuncs { - printDowncallMethods(&printer, decl) + printFunctionDowncallMethods(&printer, decl) } } } @@ -133,7 +133,7 @@ extension Swift2JavaTranslator { // Methods for funcDecl in decl.methods { - printDowncallMethods(&printer, funcDecl) + printFunctionDowncallMethods(&printer, funcDecl) } } } @@ -456,7 +456,7 @@ extension Swift2JavaTranslator { ) } - public func printDowncallMethods(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + public func printFunctionDowncallMethods(_ printer: inout CodePrinter, _ decl: ImportedFunc) { printer.printSeparator(decl.identifier) printer.printTypeDecl("private static class \(decl.baseIdentifier)") { printer in @@ -551,6 +551,7 @@ extension Swift2JavaTranslator { } } + // 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)!") @@ -561,12 +562,20 @@ extension Swift2JavaTranslator { printFunctionAddressMethod(&printer, decl: accessor, accessorKind: accessorKind) } - // Render the basic "make the downcall" function - if decl.hasParent { -// printFuncDowncallMethod(&printer, decl: decl, selfVariant: .memorySegment) -// printFuncDowncallMethod(&printer, decl: decl, selfVariant: .wrapper) - } else { -// printFuncDowncallMethod(&printer, decl: decl, selfVariant: nil) + // 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, selfVariant: .memorySegment, accessorKind: accessorKind) + printFuncDowncallMethod(&printer, decl: accessor, selfVariant: .wrapper, accessorKind: accessorKind) + } else { + printFuncDowncallMethod(&printer, decl: accessor, selfVariant: nil, accessorKind: accessorKind) + } } } @@ -594,7 +603,8 @@ extension Swift2JavaTranslator { public func printFuncDowncallMethod( _ printer: inout CodePrinter, decl: ImportedFunc, - selfVariant: SelfParameterVariant? + selfVariant: SelfParameterVariant?, + accessorKind: VariableAccessorKind? = nil ) { let returnTy = decl.returnType.javaType @@ -605,32 +615,37 @@ extension Swift2JavaTranslator { 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 selfVariant == 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, selfVariant: .wrapper))) { - \(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardParams(decl, selfVariant: .wrapper))); + \(javaDocComment) + public \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) { + \(maybeReturnCast) \(identifier)(\(renderForwardParams(decl, selfVariant: .wrapper))); } """ ) return } + let handleName = accessorKind.renderHandleFieldName printer.print( """ - /** - * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") - * } - */ - public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: selfVariant))) { - var mh$ = \(decl.baseIdentifier).HANDLE; + \(javaDocComment) + public static \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, selfVariant: selfVariant))) { + var mh$ = \(decl.baseIdentifier).\(handleName); try { if (TRACE_DOWNCALLS) { traceDowncall(\(renderForwardParams(decl, selfVariant: .memorySegment))); diff --git a/Tests/JExtractSwiftTests/FuncImportTests.swift b/Tests/JExtractSwiftTests/FuncImportTests.swift index c2eb5c27..0a2cd1b6 100644 --- a/Tests/JExtractSwiftTests/FuncImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncImportTests.swift @@ -62,7 +62,7 @@ final class MethodImportTests { } """ - @Test + @Test("Import: public func helloWorld()") func method_helloWorld() async throws { let st = Swift2JavaTranslator( javaPackage: "com.example.swift", @@ -102,8 +102,8 @@ final class MethodImportTests { ) } - @Test - func method_globalTakeInt() async throws { + @Test("Import: public func globalTakeInt(i: Int)") + func func_globalTakeInt() async throws { let st = Swift2JavaTranslator( javaPackage: "com.example.swift", swiftModuleName: "__FakeModule" @@ -144,8 +144,8 @@ final class MethodImportTests { ) } - @Test - func method_globalTakeIntLongString() async throws { + @Test("Import: public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)") + func func_globalTakeIntLongString() async throws { let st = Swift2JavaTranslator( javaPackage: "com.example.swift", swiftModuleName: "__FakeModule" diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 5674780e..2305d4ec 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -117,6 +117,50 @@ final class VariableImportTests { public static MemorySegment counterInt$set$address() { return counterInt.ADDR_SET; } + /** + * Downcall to Swift: + * + */ + public static long getCounterInt(java.lang.foreign.MemorySegment self$) { + var mh$ = counterInt.HANDLE_GET; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(self$); + } + return (long) mh$.invokeExact(self$); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * Downcall to Swift: + * + */ + public long getCounterInt() { + return (long) getCounterInt($memorySegment()); + } + /** + * Downcall to Swift: + * + */ + public static void setCounterInt(long newValue, java.lang.foreign.MemorySegment self$) { + var mh$ = counterInt.HANDLE_SET; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(newValue, self$); + } + mh$.invokeExact(newValue, self$); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * Downcall to Swift: + * + */ + public void setCounterInt(long newValue) { + setCounterInt(newValue, $memorySegment()); + } """ ) } From 5986b3005ae877f6af26b6b08f8b9245cefd4185 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 8 Oct 2024 22:48:14 +0900 Subject: [PATCH 4/5] jextract: end to end run for a property import --- Samples/SwiftKitSampleApp/build.gradle | 6 +- .../swift/generated/MySwiftClassTest.java | 21 ++++++- .../ImportedDecls+Printing.swift | 8 +++ .../Swift2JavaTranslator+Printing.swift | 55 ++++++++----------- Sources/JExtractSwift/Swift2JavaVisitor.swift | 4 +- .../JExtractSwiftTests/FuncImportTests.swift | 8 +++ .../FunctionDescriptorImportTests.swift | 10 ++-- .../VariableImportTests.swift | 10 +++- 8 files changed, 78 insertions(+), 44 deletions(-) diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle index 158fdfcf..28304626 100644 --- a/Samples/SwiftKitSampleApp/build.gradle +++ b/Samples/SwiftKitSampleApp/build.gradle @@ -95,7 +95,11 @@ application { task jextract(type: Exec) { description = "Extracts Java accessor sources using jextract" outputs.dir(layout.buildDirectory.dir("generated")) - inputs.dir("$rootDir/Sources/ExampleSwiftLibrary") + inputs.dir("$rootDir/Sources/ExampleSwiftLibrary") // monitored library + + // any changes in the source generator sources also mean the resulting output might change + inputs.dir("$rootDir/Sources/JExtractSwift") + inputs.dir("$rootDir/Sources/JExtractSwiftTool") workingDir = rootDir commandLine "make" diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java index 1a5f0d5a..740e711b 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java @@ -15,6 +15,7 @@ package com.example.swift.generated; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -31,6 +32,24 @@ static void beforeAll() { System.setProperty("jextract.trace.downcalls", "true"); } - // TODO: test member methods on MySwiftClass + @Test + void test_MySwiftClass_voidMethod() { + MySwiftClass o = new MySwiftClass(12, 42); + o.voidMethod(); + } + + @Test + void test_MySwiftClass_makeIntMethod() { + MySwiftClass o = new MySwiftClass(12, 42); + var got = o.makeIntMethod(); + assertEquals(12, got); + } + + @Test + void test_MySwiftClass_property_len() { + MySwiftClass o = new MySwiftClass(12, 42); + var got = o.makeIntMethod(); + assertEquals(12, got); + } } diff --git a/Sources/JExtractSwift/ImportedDecls+Printing.swift b/Sources/JExtractSwift/ImportedDecls+Printing.swift index 84f718e0..76422d09 100644 --- a/Sources/JExtractSwift/ImportedDecls+Printing.swift +++ b/Sources/JExtractSwift/ImportedDecls+Printing.swift @@ -34,6 +34,14 @@ extension ImportedFunc { } extension VariableAccessorKind { + + public var fieldSuffix: String { + switch self { + case .get: "_GET" + case .set: "_SET" + } + } + public var renderDescFieldName: String { switch self { case .get: "DESC_GET" diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 3f021d07..db35a1c4 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -129,7 +129,9 @@ extension Swift2JavaTranslator { } // Properties - // TODO: property accessors + for varDecl in decl.variables { + printVariableDowncallMethods(&printer, varDecl) + } // Methods for funcDecl in decl.methods { @@ -482,8 +484,8 @@ extension Swift2JavaTranslator { decl: ImportedFunc, accessorKind: VariableAccessorKind? = nil) { - let addrName = accessorKind?.renderAddrFieldName ?? "ADDR" - let methodNameSegment = accessorKind?.renderMethodNameSegment ?? "" + let addrName = accessorKind.renderAddrFieldName + let methodNameSegment = accessorKind.renderMethodNameSegment let snippet = decl.renderCommentSnippet ?? "* " printer.print( @@ -502,8 +504,8 @@ extension Swift2JavaTranslator { private func printFunctionMethodHandleMethod(_ printer: inout CodePrinter, decl: ImportedFunc, accessorKind: VariableAccessorKind? = nil) { - let handleName = accessorKind?.renderHandleFieldName ?? "HANDLE" - let methodNameSegment = accessorKind?.renderMethodNameSegment ?? "" + let handleName = accessorKind.renderHandleFieldName + let methodNameSegment = accessorKind.renderMethodNameSegment let snippet = decl.renderCommentSnippet ?? "* " printer.print( @@ -522,8 +524,8 @@ extension Swift2JavaTranslator { private func printFunctionDescriptorMethod(_ printer: inout CodePrinter, decl: ImportedFunc, accessorKind: VariableAccessorKind? = nil) { - let descName = accessorKind?.renderDescFieldName ?? "DESC" - let methodNameSegment = accessorKind?.renderMethodNameSegment ?? "" + let descName = accessorKind.renderDescFieldName + let methodNameSegment = accessorKind.renderMethodNameSegment let snippet = decl.renderCommentSnippet ?? "* " printer.print( @@ -544,10 +546,14 @@ extension Swift2JavaTranslator { printer.printTypeDecl("private static class \(decl.baseIdentifier)") { printer in for accessorKind in decl.supportedAccessorKinds { - printPropertyAccessorDescriptorValue(&printer, decl, accessorKind) -// printFunctionDescriptorValue(&printer, decl); -// printFindMemorySegmentAddrByMangledName(&printer, decl) -// printMethodDowncallHandleForAddrDesc(&printer) + guard let accessor = decl.accessorFunc(kind: accessorKind) else { + log.warning("Skip print for \(accessorKind) of \(decl.identifier)!") + continue + } + + printFunctionDescriptorValue(&printer, accessor, accessorKind: accessorKind); + printFindMemorySegmentAddrByMangledName(&printer, accessor, accessorKind: accessorKind) + printMethodDowncallHandleForAddrDesc(&printer, accessorKind: accessorKind) } } @@ -579,23 +585,19 @@ extension Swift2JavaTranslator { } } - func printFindMemorySegmentAddrByMangledName(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + func printFindMemorySegmentAddrByMangledName(_ printer: inout CodePrinter, _ decl: ImportedFunc, + accessorKind: VariableAccessorKind? = nil) { printer.print( """ - /** - * {@snippet lang = Swift: - * \(decl.displayName) - * } - */ - public static final MemorySegment ADDR = \(swiftModuleName).findOrThrow("\(decl.swiftMangledName)"); + public static final MemorySegment \(accessorKind.renderAddrFieldName) = \(swiftModuleName).findOrThrow("\(decl.swiftMangledName)"); """ ); } - func printMethodDowncallHandleForAddrDesc(_ printer: inout CodePrinter) { + func printMethodDowncallHandleForAddrDesc(_ printer: inout CodePrinter, accessorKind: VariableAccessorKind? = nil) { printer.print( """ - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static final MethodHandle \(accessorKind.renderHandleFieldName) = Linker.nativeLinker().downcallHandle(\(accessorKind.renderAddrFieldName), \(accessorKind.renderDescFieldName)); """ ) } @@ -781,7 +783,8 @@ extension Swift2JavaTranslator { public func printFunctionDescriptorValue( _ printer: inout CodePrinter, _ decl: ImportedFunc, - fieldName: String = "DESC") { + accessorKind: VariableAccessorKind? = nil) { + let fieldName = accessorKind.renderDescFieldName printer.start("public static final FunctionDescriptor \(fieldName) = ") let parameterLayoutDescriptors = javaMemoryLayoutDescriptors( @@ -818,14 +821,4 @@ extension Swift2JavaTranslator { printer.print(");"); } - public func printPropertyAccessorDescriptorValue( - _ printer: inout CodePrinter, - _ decl: ImportedVariable, - _ kind: VariableAccessorKind) { - guard let funcDecl = decl.accessorFunc(kind: kind) else { - return - } - - printFunctionDescriptorValue(&printer, funcDecl, fieldName: kind.renderDescFieldName) - } } diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index db42c02b..8cf39eab 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -131,15 +131,13 @@ final class Swift2JavaVisitor: SyntaxVisitor { } override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { - self.log.warning("NODE: \(node.debugDescription)") - guard let binding = node.bindings.first else { return .skipChildren } let fullName = "\(binding.pattern.trimmed)" - // TODO filter out kinds of variables we cannot import + // TODO: filter out kinds of variables we cannot import self.log.info("Import variable: \(node.kind) \(fullName)") diff --git a/Tests/JExtractSwiftTests/FuncImportTests.swift b/Tests/JExtractSwiftTests/FuncImportTests.swift index 0a2cd1b6..5a6c0f38 100644 --- a/Tests/JExtractSwiftTests/FuncImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncImportTests.swift @@ -83,6 +83,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func helloWorld() * } @@ -125,6 +126,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func globalTakeInt(i: Int) * } @@ -167,6 +169,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func globalTakeIntLongString(i32: Int32, l: Int64, s: String) * } @@ -209,6 +212,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func helloMemberFunction() * } @@ -251,6 +255,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func helloMemberInExtension() * } @@ -293,6 +298,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func helloMemberFunction() * } @@ -335,6 +341,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func helloMemberFunction() * } @@ -369,6 +376,7 @@ final class MethodImportTests { expected: """ /** + * Downcall to Swift: * {@snippet lang=swift : * public func makeInt() -> Int * } diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index f52757a4..4199ca16 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -162,8 +162,8 @@ extension FunctionDescriptorTests { } func variableAccessorDescriptorTest( - _ methodIdentifier: String, - _ kind: VariableAccessorKind, + _ identifier: String, + _ accessorKind: VariableAccessorKind, javaPackage: String = "com.example.swift", swiftModuleName: String = "SwiftModule", logLevel: Logger.Level = .trace, @@ -180,15 +180,15 @@ extension FunctionDescriptorTests { let varDecl: ImportedVariable? = st.importedTypes.values.compactMap { $0.variables.first { - $0.identifier == methodIdentifier + $0.identifier == identifier } }.first guard let varDecl else { - fatalError("Cannot find descriptor of: \(methodIdentifier)") + fatalError("Cannot find descriptor of: \(identifier)") } let getOutput = CodePrinter.toString { printer in - st.printPropertyAccessorDescriptorValue(&printer, varDecl, kind) + st.printFunctionDescriptorValue(&printer, varDecl.accessorFunc(kind: accessorKind)!, accessorKind: accessorKind) } try await body(getOutput) diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 2305d4ec..f59b8ff4 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -67,13 +67,17 @@ final class VariableImportTests { // counterInt private static class counterInt { public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( - /* -> */SWIFT_INT, + /* -> */SWIFT_INT, SWIFT_POINTER - ); + ); + public static final MemorySegment ADDR_GET = __FakeModule.findOrThrow("g"); + public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET); public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( - SWIFT_INT, + SWIFT_INT, SWIFT_POINTER ); + public static final MemorySegment ADDR_SET = __FakeModule.findOrThrow("s"); + public static final MethodHandle HANDLE_SET = Linker.nativeLinker().downcallHandle(ADDR_SET, DESC_SET); } /** * Function descriptor for: From 9a561cf85f10ad0fb808c8259ad75a570c164a12 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 8 Oct 2024 23:07:50 +0900 Subject: [PATCH 5/5] disable runtime tests on linux until we have mangling --- .../java/com/example/swift/generated/MySwiftClassTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java index 740e711b..8ca3f96b 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java @@ -16,6 +16,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -33,12 +35,14 @@ static void beforeAll() { } @Test + @DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces void test_MySwiftClass_voidMethod() { MySwiftClass o = new MySwiftClass(12, 42); o.voidMethod(); } @Test + @DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces void test_MySwiftClass_makeIntMethod() { MySwiftClass o = new MySwiftClass(12, 42); var got = o.makeIntMethod(); @@ -46,6 +50,7 @@ void test_MySwiftClass_makeIntMethod() { } @Test + @DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces void test_MySwiftClass_property_len() { MySwiftClass o = new MySwiftClass(12, 42); var got = o.makeIntMethod();