From c99e58f2b30f098fb016bae46be37807fe74ac11 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 27 Sep 2024 11:53:31 -0700 Subject: [PATCH 1/4] Move more type handling over to TranslatedType so we can centralize there --- Sources/JExtractSwift/ImportedDecls.swift | 39 ++++++++++++++++--- Sources/JExtractSwift/JavaTypes.swift | 7 ---- .../Swift2JavaTranslator+Printing.swift | 10 ++--- .../JExtractSwift/Swift2JavaTranslator.swift | 14 ++++++- Sources/JExtractSwift/Swift2JavaVisitor.swift | 26 ++++++------- Sources/JExtractSwift/SwiftDylib.swift | 8 ++-- Sources/JExtractSwift/TranslatedType.swift | 21 +++++++++- Sources/JavaTypes/JavaType+SwiftNames.swift | 4 -- 8 files changed, 87 insertions(+), 42 deletions(-) diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index d6e0119e..62fe2c95 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -28,16 +28,27 @@ public struct ImportedProtocol: ImportedDecl { /// 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 var name: ImportedTypeName + public let swiftTypeName: String + public let javaType: JavaType + public var swiftMangledName: String? public var kind: NominalTypeKind public var initializers: [ImportedFunc] = [] public var methods: [ImportedFunc] = [] - public init(name: ImportedTypeName, kind: NominalTypeKind) { - self.name = name + public init(swiftTypeName: String, javaType: JavaType, swiftMangledName: String? = nil, kind: NominalTypeKind) { + self.swiftTypeName = swiftTypeName + self.javaType = javaType + self.swiftMangledName = swiftMangledName self.kind = kind } + + var importedTypeName: ImportedTypeName { + ImportedTypeName( + swiftTypeName: swiftTypeName, + javaType: javaType + ) + } } public enum NominalTypeKind { @@ -172,13 +183,31 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { case .pointer: let selfParam: FunctionParameterSyntax = "self$: $swift_pointer" params.append( - ImportedParam(param: selfParam, type: java_lang_foreign_MemorySegment(swiftTypeName: "Self.self")) + ImportedParam( + param: selfParam, + type: TranslatedType( + cCompatibleConvention: .indirect, + originalSwiftType: "\(raw: parentName.swiftTypeName)", + cCompatibleSwiftType: "UnsafeRawPointer", + cCompatibleJavaMemoryLayout: .memorySegment, + javaType: .javaForeignMemorySegment + ).importedTypeName + ) ) case .memorySegment: let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment" params.append( - ImportedParam(param: selfParam, type: java_lang_foreign_MemorySegment(swiftTypeName: "")) + ImportedParam( + param: selfParam, + type: TranslatedType( + cCompatibleConvention: .indirect, + originalSwiftType: "\(raw: parentName.swiftTypeName)", + cCompatibleSwiftType: "UnsafeRawPointer", + cCompatibleJavaMemoryLayout: .memorySegment, + javaType: .javaForeignMemorySegment + ).importedTypeName + ) ) case .wrapper: diff --git a/Sources/JExtractSwift/JavaTypes.swift b/Sources/JExtractSwift/JavaTypes.swift index 1c526583..e38df600 100644 --- a/Sources/JExtractSwift/JavaTypes.swift +++ b/Sources/JExtractSwift/JavaTypes.swift @@ -13,13 +13,6 @@ import JavaTypes -func java_lang_foreign_MemorySegment(swiftTypeName: String) -> ImportedTypeName { - ImportedTypeName( - swiftTypeName: swiftTypeName, - javaType: .javaForeignMemorySegment - ) -} - extension JavaType { /// The description of the type java.lang.foreign.MemorySegment. static var javaForeignMemorySegment: JavaType { diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 158b44ef..57e11b52 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -28,7 +28,7 @@ extension Swift2JavaTranslator { var printer = CodePrinter() for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let filename = "\(ty.name.javaClassName!).java" + let filename = "\(ty.javaType).java" log.info("Printing contents: \(filename)") printImportedClass(&printer, ty) @@ -116,7 +116,7 @@ extension Swift2JavaTranslator { printer.print( """ // FIXME: this detecting is somewhat off - public static final String TYPE_METADATA_NAME = "\(decl.name.swiftMangledName)"; + public static final String TYPE_METADATA_NAME = "\(decl.swiftMangledName!)"; static final MemorySegment TYPE_METADATA = SwiftKit.getTypeByMangledNameInEnvironment(TYPE_METADATA_NAME); """ ) @@ -165,7 +165,7 @@ extension Swift2JavaTranslator { } public func printClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void) { - printer.printTypeDecl("public final class \(decl.name.javaClassName!)") { printer in + printer.printTypeDecl("public final class \(decl.javaType)") { printer in // ==== Storage of the class // FIXME: implement the self storage for the memory address and accessors printClassSelfProperty(&printer, decl) @@ -274,7 +274,7 @@ extension Swift2JavaTranslator { printer.print( """ /** Instances are created using static {@code init} methods rather than through the constructor directly. */ - private \(decl.name.javaClassName!)(MemorySegment selfMemorySegment) { + private \(decl.javaType)(MemorySegment selfMemorySegment) { this.selfMemorySegment = selfMemorySegment; } """ @@ -307,7 +307,7 @@ extension Swift2JavaTranslator { // SWIFT_INT.withName("heapObject"), // ... // SWIFT_INT.withName("cap") - ).withName("\(decl.name.javaClassName!)"); // TODO: is the name right? + ).withName("\(decl.javaType)"); // TODO: is the name right? /** * When other types refer to this type, they refer to by a pointer, diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index d5396872..d64a7212 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// import Foundation +import JavaTypes import SwiftBasicFormat import SwiftParser import SwiftSyntax @@ -56,6 +57,15 @@ 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 } + public func analyze( swiftInterfacePath: String, text: String? = nil @@ -126,11 +136,11 @@ extension Swift2JavaTranslator { importedTypes = Dictionary(uniqueKeysWithValues: try await importedTypes._mapAsync { (tyName, tyDecl) in var tyDecl = tyDecl - log.info("Mapping type: \(tyDecl.name)") + log.info("Mapping type: \(tyDecl.swiftTypeName)") tyDecl = try await dylib.fillInTypeMangledName(tyDecl) - log.info("Mapping members of: \(tyDecl.name)") + log.info("Mapping members of: \(tyDecl.swiftTypeName)") tyDecl.initializers = try await tyDecl.initializers._mapAsync { initDecl in dylib.log.logLevel = .trace diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index db4625bb..b936bca0 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -57,14 +57,12 @@ final class Swift2JavaVisitor: SyntaxVisitor { } let importedNominal = ImportedNominalType( - name: ImportedTypeName( - swiftTypeName: fullName, - javaType: .class( - package: targetJavaPackage, - name: fullName - ), - swiftMangledName: nominal.mangledNameFromComment + swiftTypeName: fullName, + javaType: .class( + package: targetJavaPackage, + name: fullName ), + swiftMangledName: nominal.mangledNameFromComment, kind: kind ) @@ -77,7 +75,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - currentTypeName = importedNominalType.name.swiftTypeName + currentTypeName = importedNominalType.swiftTypeName return .visitChildren } @@ -96,7 +94,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - currentTypeName = importedNominalType.name.swiftTypeName + currentTypeName = importedNominalType.swiftTypeName return .visitChildren } @@ -149,7 +147,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { let fullName = "\(node.name.text)(\(argumentLabelsStr))" var funcDecl = ImportedFunc( - parentName: currentTypeName.map { translator.importedTypes[$0] }??.name, + parentName: currentTypeName.map { translator.importedTypes[$0] }??.importedTypeName, identifier: fullName, returnType: javaResultType, parameters: params @@ -180,7 +178,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - self.log.info("Import initializer: \(node.kind) \(currentType.name.javaType.description)") + self.log.info("Import initializer: \(node.kind) \(currentType.javaType.description)") let params: [ImportedParam] do { params = try node.signature.parameterClause.parameters.map { param in @@ -200,9 +198,9 @@ final class Swift2JavaVisitor: SyntaxVisitor { "init(\(params.compactMap { $0.effectiveName ?? "_" }.joined(separator: ":")))" var funcDecl = ImportedFunc( - parentName: currentType.name, + parentName: currentType.importedTypeName, identifier: initIdentifier, - returnType: currentType.name, + returnType: currentType.importedTypeName, parameters: params ) funcDecl.isInit = true @@ -213,7 +211,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { funcDecl.swiftMangledName = mangledName } - log.info("Record initializer method in \(currentType.name.javaType.description): \(funcDecl.identifier)") + log.info("Record initializer method in \(currentType.javaType.description): \(funcDecl.identifier)") translator.importedTypes[currentTypeName]!.initializers.append(funcDecl) return .skipChildren diff --git a/Sources/JExtractSwift/SwiftDylib.swift b/Sources/JExtractSwift/SwiftDylib.swift index 1f656ea7..486aa3ab 100644 --- a/Sources/JExtractSwift/SwiftDylib.swift +++ b/Sources/JExtractSwift/SwiftDylib.swift @@ -33,19 +33,19 @@ package struct SwiftDylib { // FIXME: remove this entire utility; replace with package func fillInTypeMangledName(_ decl: ImportedNominalType) async throws -> ImportedNominalType { // TODO: this is hacky, not precise at all and will be removed entirely - guard decl.name.swiftMangledName.isEmpty else { + guard decl.swiftMangledName == nil else { // it was already processed return decl } var decl = decl let names = try await nmSymbolNames(grepDemangled: [ - decl.name.swiftTypeName, + decl.swiftTypeName, "type metadata for", ]) if let name = names.first { - log.trace("Selected mangled name for '\(decl.name.javaType.description)': \(name)") - decl.name.swiftMangledName = name.mangledName + log.trace("Selected mangled name for '\(decl.javaType.description)': \(name)") + decl.swiftMangledName = name.mangledName } return decl diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index 16a4ced1..d18fe6b1 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -102,6 +102,19 @@ extension Swift2JavaVisitor { ) } + // 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)", + cCompatibleSwiftType: "Swift.\(raw: name)", + cCompatibleJavaMemoryLayout: .int, + javaType: translator.javaPrimitiveForSwiftInt + ) + } + // Identify the various pointer types from the standard library. if let (requiresArgument, _, _) = name.isNameOfSwiftPointerType { // Dig out the pointee type if needed. @@ -220,10 +233,16 @@ extension TranslatedType { javaType: javaType ) } - } + enum CCompatibleJavaMemoryLayout { + /// A primitive Java type. 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 + case memorySegment } diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index e43b15cd..088cb545 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -63,10 +63,6 @@ extension JavaType { case "Float": self = .float case "Double": self = .double case "Void": self = .void - - /// NOTE: This is only correct for 64-bit platforms. - case "Int": self = .long - default: return nil } } From 1245f75904f180972a1c65c73de06dc7c75b4644 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 27 Sep 2024 12:39:55 -0700 Subject: [PATCH 2/4] Start modeling the TranslatedType -> ForeignValueLayout mapping --- Sources/JExtractSwift/ImportedDecls.swift | 4 +- .../JavaConstants/ForeignValueLayouts.swift | 2 +- Sources/JExtractSwift/TranslatedType.swift | 44 ++++++++++++++++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index 62fe2c95..6a7f6198 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -189,7 +189,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { cCompatibleConvention: .indirect, originalSwiftType: "\(raw: parentName.swiftTypeName)", cCompatibleSwiftType: "UnsafeRawPointer", - cCompatibleJavaMemoryLayout: .memorySegment, + cCompatibleJavaMemoryLayout: .heapObject, javaType: .javaForeignMemorySegment ).importedTypeName ) @@ -204,7 +204,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { cCompatibleConvention: .indirect, originalSwiftType: "\(raw: parentName.swiftTypeName)", cCompatibleSwiftType: "UnsafeRawPointer", - cCompatibleJavaMemoryLayout: .memorySegment, + cCompatibleJavaMemoryLayout: .heapObject, javaType: .javaForeignMemorySegment ).importedTypeName ) diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift index 639c8c4a..a63f565d 100644 --- a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift +++ b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift @@ -54,7 +54,6 @@ public struct ForeignValueLayout: CustomStringConvertible { } extension ForeignValueLayout { - public static let SwiftSelf = Self(inlineComment: "Self", javaConstant: "SWIFT_SELF") public static let SwiftPointer = Self(javaConstant: "SWIFT_POINTER") public static let SwiftBool = Self(javaConstant: "SWIFT_BOOL") @@ -63,6 +62,7 @@ extension ForeignValueLayout { public static let SwiftInt64 = Self(javaConstant: "SWIFT_INT64") public static let SwiftInt32 = Self(javaConstant: "SWIFT_INT32") 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 SwiftFloat = Self(javaConstant: "SWIFT_FLOAT") diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index d18fe6b1..31343d67 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -33,7 +33,7 @@ extension Swift2JavaVisitor { cCompatibleConvention: .direct, originalSwiftType: type, cCompatibleSwiftType: "@convention(c) () -> Void", - cCompatibleJavaMemoryLayout: .memorySegment, + cCompatibleJavaMemoryLayout: .cFunction, javaType: .javaLangRunnable ) } @@ -134,7 +134,7 @@ extension Swift2JavaVisitor { cCompatibleConvention: .direct, originalSwiftType: type, cCompatibleSwiftType: "UnsafeMutableRawPointer", - cCompatibleJavaMemoryLayout: .memorySegment, + cCompatibleJavaMemoryLayout: .heapObject, javaType: .javaForeignMemorySegment ) } @@ -155,7 +155,7 @@ extension Swift2JavaVisitor { cCompatibleConvention: .indirect, originalSwiftType: type, cCompatibleSwiftType: "UnsafeMutablePointer<\(type)>", - cCompatibleJavaMemoryLayout: .memorySegment, + cCompatibleJavaMemoryLayout: .heapObject, javaType: .class(package: nil, name: name) ) } @@ -235,15 +235,49 @@ extension TranslatedType { } } +/// Describes the C-compatible layout as it should be referenced from Java. enum CCompatibleJavaMemoryLayout { - /// A primitive Java type. + /// 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 - case memorySegment + /// 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 +} + +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): + 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") + } + + case .int: + return .SwiftInt + + case .heapObject, .cFunction: + return .SwiftPointer + } + } } enum TypeTranslationError: Error { From f87dd0e53a8153bf9809da031293532773068e18 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 27 Sep 2024 13:09:41 -0700 Subject: [PATCH 3/4] Finish the migration from ImportedTypeName over to TranslatedType TranslatedType captures more information about how the type is represented in Swift, Java, and the intermediate stages in between. --- Sources/JExtractSwift/ImportedDecls.swift | 69 +++++-------------- .../Swift2JavaTranslator+MemoryLayouts.swift | 56 +-------------- .../Swift2JavaTranslator+Printing.swift | 19 +++-- Sources/JExtractSwift/Swift2JavaVisitor.swift | 21 ++---- Sources/JExtractSwift/TranslatedType.swift | 12 ++-- 5 files changed, 38 insertions(+), 139 deletions(-) diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index 6a7f6198..71c17abd 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -15,16 +15,12 @@ import Foundation import JavaTypes import SwiftSyntax -protocol ImportedDecl: Hashable { +protocol ImportedDecl { } public typealias JavaPackage = String -public struct ImportedProtocol: ImportedDecl { - public var identifier: 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 { @@ -43,9 +39,12 @@ public struct ImportedNominalType: ImportedDecl { self.kind = kind } - var importedTypeName: ImportedTypeName { - ImportedTypeName( - swiftTypeName: swiftTypeName, + var translatedType: TranslatedType { + TranslatedType( + cCompatibleConvention: .direct, + originalSwiftType: "\(raw: swiftTypeName)", + cCompatibleSwiftType: "\(raw: swiftTypeName)", + cCompatibleJavaMemoryLayout: .heapObject, javaType: javaType ) } @@ -58,7 +57,7 @@ public enum NominalTypeKind { case `struct` } -public struct ImportedParam: Hashable { +public struct ImportedParam { let param: FunctionParameterSyntax var firstName: String? { @@ -89,7 +88,7 @@ public struct ImportedParam: Hashable { } // The mapped-to Java type of the above Java type, collections and optionals may be replaced with Java ones etc. - var type: ImportedTypeName + var type: TranslatedType } extension ImportedParam { @@ -102,30 +101,6 @@ extension ImportedParam { } } -public struct ImportedTypeName: Hashable { - public var swiftTypeName: String - - public var swiftMangledName: String = "" - - public var javaType: JavaType - - public var isVoid: Bool { javaType == .void } - - public var fullyQualifiedName: String { javaType.description } - - /// Retrieve the Java class name that this type describes, or nil if it - /// doesn't represent a class at all. - public var javaClassName: String? { - javaType.className - } - - public init(swiftTypeName: String, javaType: JavaType, swiftMangledName: String? = nil) { - self.swiftTypeName = swiftTypeName - self.javaType = javaType - self.swiftMangledName = swiftMangledName ?? "" - } -} - // TODO: this is used in different contexts and needs a cleanup public enum SelfParameterVariant { /// Make a method that accepts the raw memory pointer as a MemorySegment @@ -141,7 +116,7 @@ public struct 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 parentName: ImportedTypeName? + var parentName: TranslatedType? public var hasParent: Bool { parentName != nil } /// This is a full name such as init(cap:name:). @@ -166,7 +141,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { return identifier } - public var returnType: ImportedTypeName + public var returnType: TranslatedType public var parameters: [ImportedParam] public func effectiveParameters(selfVariant: SelfParameterVariant?) -> [ImportedParam] { @@ -185,28 +160,18 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { params.append( ImportedParam( param: selfParam, - type: TranslatedType( - cCompatibleConvention: .indirect, - originalSwiftType: "\(raw: parentName.swiftTypeName)", - cCompatibleSwiftType: "UnsafeRawPointer", - cCompatibleJavaMemoryLayout: .heapObject, - javaType: .javaForeignMemorySegment - ).importedTypeName + type: parentName ) ) case .memorySegment: let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment" + var parentForSelf = parentName + parentForSelf.javaType = .javaForeignMemorySegment params.append( ImportedParam( param: selfParam, - type: TranslatedType( - cCompatibleConvention: .indirect, - originalSwiftType: "\(raw: parentName.swiftTypeName)", - cCompatibleSwiftType: "UnsafeRawPointer", - cCompatibleJavaMemoryLayout: .heapObject, - javaType: .javaForeignMemorySegment - ).importedTypeName + type: parentForSelf ) ) @@ -230,9 +195,9 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { public var isInit: Bool = false public init( - parentName: ImportedTypeName?, + parentName: TranslatedType?, identifier: String, - returnType: ImportedTypeName, + returnType: TranslatedType, parameters: [ImportedParam] ) { self.parentName = parentName diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift index 8378a15f..5109446b 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift @@ -16,8 +16,6 @@ import SwiftBasicFormat import SwiftParser import SwiftSyntax -let SWIFT_POINTER = "SWIFT_POINTER" - extension Swift2JavaTranslator { public func javaMemoryLayoutDescriptors( forParametersOf decl: ImportedFunc, @@ -31,61 +29,9 @@ extension Swift2JavaTranslator { // decl.isInit ? nil : .wrapper for param in decl.effectiveParameters(selfVariant: selfVariant) { - if let paramLayout = javaMemoryLayoutDescriptor(param.type) { - layouts.append(paramLayout) - } + layouts.append(param.type.foreignValueLayout) } return layouts } - - // This may reach for another types $layout I think - public func javaMemoryLayoutDescriptor(_ ty: ImportedTypeName) -> ForeignValueLayout? { - switch ty.swiftTypeName { - case "Bool": - return .SwiftBool - case "Int": - return .SwiftInt - case "Int32": - return .SwiftInt32 - case "Int64": - return .SwiftInt64 - case "Float": - return .SwiftFloat - case "Double": - return .SwiftDouble - case "Void": - return nil - case "Never": - return nil - case "Swift.UnsafePointer": - return .SwiftPointer - default: - break - } - - // not great? - if ty.swiftTypeName == "Self.self" { - return .SwiftPointer - } - - // not great? - if ty.swiftTypeName == "(any Any.Type)?" { - return .SwiftPointer - } - - if ty.swiftTypeName == "() -> ()" { - return .SwiftPointer - } - - // TODO: Java has OptionalLong, OptionalInt, OptionalDouble types. - // if ty.swiftTypeName.hasSuffix("?") { - // if ty.swiftTypeName == "Int?" { - // return JavaOptionalLong - // } else .. - // } - - // Last fallback is to try to get the type's $layout() - return ForeignValueLayout(inlineComment: ty.swiftTypeName, customType: ty.fullyQualifiedName) - } } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 57e11b52..262a6ccd 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -421,7 +421,7 @@ extension Swift2JavaTranslator { * \(decl.swiftDeclRaw ?? "") * } */ - public static \(parentName.javaClassName!) init(\(renderJavaParamDecls(decl, selfVariant: .none))) { + public static \(parentName.javaType) init(\(renderJavaParamDecls(decl, selfVariant: .none))) { var mh$ = \(descClassIdentifier).HANDLE; try { if (TRACE_DOWNCALLS) { @@ -429,7 +429,7 @@ extension Swift2JavaTranslator { } var self = (MemorySegment) mh$.invokeExact(\(renderForwardParams(decl, selfVariant: nil)), TYPE_METADATA); - return new \(parentName.javaClassName!)(self); + return new \(parentName.javaType)(self); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -524,10 +524,10 @@ extension Swift2JavaTranslator { decl: ImportedFunc, selfVariant: SelfParameterVariant? ) { - let returnTy = decl.returnType.fullyQualifiedName + let returnTy = decl.returnType.javaType let maybeReturnCast: String - if decl.returnType.isVoid { + if decl.returnType.javaType == .void { maybeReturnCast = "" // nothing to return or cast to } else { maybeReturnCast = "return (\(returnTy))" @@ -601,7 +601,7 @@ extension Swift2JavaTranslator { } for p in decl.effectiveParameters(selfVariant: selfVariant) { - let param = "\(p.type.fullyQualifiedName) \(p.effectiveName ?? nextUniqueParamName())" + let param = "\(p.type.javaType.description) \(p.effectiveName ?? nextUniqueParamName())" ps.append(param) } @@ -641,7 +641,7 @@ extension Swift2JavaTranslator { selfVariant: .pointer ) - if decl.returnType.isVoid { + if decl.returnType.javaType == .void { printer.print("FunctionDescriptor.ofVoid("); printer.indent() } else { @@ -655,10 +655,9 @@ extension Swift2JavaTranslator { // when initializing, we return a pointer to the newly created object printer.print("/* -> */\(ForeignValueLayout.SwiftPointer)", .parameterNewlineSeparator(returnTyIsLastTy)) } else { - if var returnDesc = javaMemoryLayoutDescriptor(decl.returnType) { - returnDesc.inlineComment = " -> " - printer.print(returnDesc, .parameterNewlineSeparator(returnTyIsLastTy)) - } + var returnDesc = decl.returnType.foreignValueLayout + returnDesc.inlineComment = " -> " + printer.print(returnDesc, .parameterNewlineSeparator(returnTyIsLastTy)) } } diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index b936bca0..57f906ba 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -120,18 +120,18 @@ final class Swift2JavaVisitor: SyntaxVisitor { } let params: [ImportedParam] - let javaResultType: ImportedTypeName + let javaResultType: TranslatedType do { params = try node.signature.parameterClause.parameters.map { param in // TODO: more robust parameter handling // TODO: More robust type handling return ImportedParam( param: param, - type: try mapTypeToJava(name: param.type) + type: try cCompatibleType(for: param.type) ) } - javaResultType = try mapTypeToJava(name: returnTy) + javaResultType = try cCompatibleType(for: returnTy) } catch { self.log.info("Unable to import function \(node.name) - \(error)") return .skipChildren @@ -147,7 +147,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { let fullName = "\(node.name.text)(\(argumentLabelsStr))" var funcDecl = ImportedFunc( - parentName: currentTypeName.map { translator.importedTypes[$0] }??.importedTypeName, + parentName: currentTypeName.map { translator.importedTypes[$0] }??.translatedType, identifier: fullName, returnType: javaResultType, parameters: params @@ -186,7 +186,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { // TODO: More robust type handling return ImportedParam( param: param, - type: try mapTypeToJava(name: param.type) + type: try cCompatibleType(for: param.type) ) } } catch { @@ -198,9 +198,9 @@ final class Swift2JavaVisitor: SyntaxVisitor { "init(\(params.compactMap { $0.effectiveName ?? "_" }.joined(separator: ":")))" var funcDecl = ImportedFunc( - parentName: currentType.importedTypeName, + parentName: currentType.translatedType, identifier: initIdentifier, - returnType: currentType.importedTypeName, + returnType: currentType.translatedType, parameters: params ) funcDecl.isInit = true @@ -254,13 +254,6 @@ extension FunctionDeclSyntax { } } -extension Swift2JavaVisitor { - // TODO: this is more more complicated, we need to know our package and imports etc - func mapTypeToJava(name swiftType: TypeSyntax) throws -> ImportedTypeName { - return try cCompatibleType(for: swiftType).importedTypeName - } -} - private let mangledNameCommentPrefix = "MANGLED NAME: " extension SyntaxProtocol { diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index 31343d67..a376896a 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -207,7 +207,7 @@ enum ParameterConvention { case indirect } -struct TranslatedType { +public struct TranslatedType { /// How a parameter of this type will be passed through C functions. var cCompatibleConvention: ParameterConvention @@ -224,14 +224,10 @@ struct TranslatedType { /// The Java type that is used to present these values in Java. var javaType: JavaType -} -extension TranslatedType { - var importedTypeName: ImportedTypeName { - ImportedTypeName( - swiftTypeName: originalSwiftType.trimmedDescription, - javaType: javaType - ) + /// Produce a Swift type name to reference this type. + var swiftTypeName: String { + originalSwiftType.trimmedDescription } } From 885b45ab40af7b79687889e6e528fda9e22176f2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 27 Sep 2024 13:40:28 -0700 Subject: [PATCH 4/4] Go through nominal type resolution when translating types. When determining how to translate a Swift nominal type for Java, go through the nominal lookup mechanism so we will (1) only find types that we actually know about, rather than guessing, and (2) lazily form the imported type record so we can handle seeing types out-of-order. --- Sources/JExtractSwift/ImportedDecls.swift | 2 +- .../JExtractSwift/Swift2JavaTranslator.swift | 45 +++++++++++++++++++ Sources/JExtractSwift/Swift2JavaVisitor.swift | 36 +-------------- Sources/JExtractSwift/TranslatedType.swift | 24 ++++------ .../JExtractSwiftTests/FuncImportTests.swift | 8 +++- 5 files changed, 64 insertions(+), 51 deletions(-) diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index 71c17abd..8ae683dc 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -43,7 +43,7 @@ public struct ImportedNominalType: ImportedDecl { TranslatedType( cCompatibleConvention: .direct, originalSwiftType: "\(raw: swiftTypeName)", - cCompatibleSwiftType: "\(raw: swiftTypeName)", + cCompatibleSwiftType: "UnsafeRawPointer", cCompatibleJavaMemoryLayout: .heapObject, javaType: javaType ) diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index d64a7212..d66e23c2 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -182,6 +182,51 @@ extension Swift2JavaTranslator { } +// ==== ---------------------------------------------------------------------------------------------------------------- +// MARK: Type translation +extension Swift2JavaTranslator { + /// Try to resolve the given nominal type node into its imported + /// representation. + func importedNominalType( + _ nominal: some DeclGroupSyntax & NamedDeclSyntax + ) -> ImportedNominalType? { + if !nominal.shouldImport(log: log) { + return nil + } + + guard let fullName = nominalResolution.fullyQualifiedName(of: nominal) else { + return nil + } + + if let alreadyImported = importedTypes[fullName] { + return alreadyImported + } + + // 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 + default: return nil + } + + let importedNominal = ImportedNominalType( + swiftTypeName: fullName, + javaType: .class( + package: javaPackage, + name: fullName + ), + swiftMangledName: nominal.mangledNameFromComment, + kind: kind + ) + + importedTypes[fullName] = importedNominal + return importedNominal + } +} + // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Errors diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 57f906ba..a4190889 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -38,40 +38,8 @@ final class Swift2JavaVisitor: SyntaxVisitor { super.init(viewMode: .all) } - /// Try to resolve the given nominal type node into its imported - /// representation. - func resolveNominalType( - _ nominal: some DeclGroupSyntax & NamedDeclSyntax, - kind: NominalTypeKind - ) -> ImportedNominalType? { - if !nominal.shouldImport(log: log) { - return nil - } - - guard let fullName = translator.nominalResolution.fullyQualifiedName(of: nominal) else { - return nil - } - - if let alreadyImported = translator.importedTypes[fullName] { - return alreadyImported - } - - let importedNominal = ImportedNominalType( - swiftTypeName: fullName, - javaType: .class( - package: targetJavaPackage, - name: fullName - ), - swiftMangledName: nominal.mangledNameFromComment, - kind: kind - ) - - translator.importedTypes[fullName] = importedNominal - return importedNominal - } - override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - guard let importedNominalType = resolveNominalType(node, kind: .class) else { + guard let importedNominalType = translator.importedNominalType(node) else { return .skipChildren } @@ -90,7 +58,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { // 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 = resolveNominalType(nominal, kind: .class) else { + let importedNominalType = translator.importedNominalType(nominal) else { return .skipChildren } diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index a376896a..1f6ddd20 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -116,7 +116,7 @@ extension Swift2JavaVisitor { } // Identify the various pointer types from the standard library. - if let (requiresArgument, _, _) = name.isNameOfSwiftPointerType { + if let (requiresArgument, _, hasCount) = name.isNameOfSwiftPointerType, !hasCount { // Dig out the pointee type if needed. if requiresArgument { guard let genericArguments else { @@ -144,20 +144,14 @@ extension Swift2JavaVisitor { throw TypeTranslationError.unexpectedGenericArguments(type, genericArguments) } - // Nested types aren't mapped into Java. - if parent != nil { - throw TypeTranslationError.nestedType(type) + // 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 { + throw TypeTranslationError.unknown(type) } - // Swift types are passed indirectly. - // FIXME: Look up type names to figure out which ones are exposed and how. - return TranslatedType( - cCompatibleConvention: .indirect, - originalSwiftType: type, - cCompatibleSwiftType: "UnsafeMutablePointer<\(type)>", - cCompatibleJavaMemoryLayout: .heapObject, - javaType: .class(package: nil, name: name) - ) + return importedNominal.translatedType } } @@ -286,6 +280,6 @@ enum TypeTranslationError: Error { /// Missing generic arguments. case missingGenericArguments(TypeSyntax) - /// Type syntax nodes cannot be mapped. - case nestedType(TypeSyntax) + /// Unknown nominal type. + case unknown(TypeSyntax) } diff --git a/Tests/JExtractSwiftTests/FuncImportTests.swift b/Tests/JExtractSwiftTests/FuncImportTests.swift index 04158f8f..3b4695c7 100644 --- a/Tests/JExtractSwiftTests/FuncImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncImportTests.swift @@ -53,6 +53,12 @@ final class MethodImportTests { @objc deinit } + + // FIXME: Hack to allow us to translate "String", even though it's not + // actually available + // MANGLED NAME: $ss + public class String { + } """ @Test func method_helloWorld() async throws { @@ -161,7 +167,7 @@ final class MethodImportTests { * public func globalTakeIntLongString(i32: Int32, l: Int64, s: String) * } */ - public static void globalTakeIntLongString(int i32, long l, String s) { + public static void globalTakeIntLongString(int i32, long l, com.example.swift.String s) { var mh$ = globalTakeIntLongString.HANDLE; try { if (TRACE_DOWNCALLS) {