diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index d6e0119e..8ae683dc 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -15,29 +15,39 @@ 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 { - 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 translatedType: TranslatedType { + TranslatedType( + cCompatibleConvention: .direct, + originalSwiftType: "\(raw: swiftTypeName)", + cCompatibleSwiftType: "UnsafeRawPointer", + cCompatibleJavaMemoryLayout: .heapObject, + javaType: javaType + ) + } } public enum NominalTypeKind { @@ -47,7 +57,7 @@ public enum NominalTypeKind { case `struct` } -public struct ImportedParam: Hashable { +public struct ImportedParam { let param: FunctionParameterSyntax var firstName: String? { @@ -78,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 { @@ -91,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 @@ -130,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:). @@ -155,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] { @@ -172,13 +158,21 @@ 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: parentName + ) ) case .memorySegment: let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment" + var parentForSelf = parentName + parentForSelf.javaType = .javaForeignMemorySegment params.append( - ImportedParam(param: selfParam, type: java_lang_foreign_MemorySegment(swiftTypeName: "")) + ImportedParam( + param: selfParam, + type: parentForSelf + ) ) case .wrapper: @@ -201,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/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/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+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 158b44ef..262a6ccd 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, @@ -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/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index d5396872..d66e23c2 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 @@ -172,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 db4625bb..a4190889 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -38,46 +38,12 @@ 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( - name: ImportedTypeName( - 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 } - currentTypeName = importedNominalType.name.swiftTypeName + currentTypeName = importedNominalType.swiftTypeName return .visitChildren } @@ -92,11 +58,11 @@ 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 } - currentTypeName = importedNominalType.name.swiftTypeName + currentTypeName = importedNominalType.swiftTypeName return .visitChildren } @@ -122,18 +88,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 @@ -149,7 +115,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] }??.translatedType, identifier: fullName, returnType: javaResultType, parameters: params @@ -180,7 +146,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 @@ -188,7 +154,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 { @@ -200,9 +166,9 @@ final class Swift2JavaVisitor: SyntaxVisitor { "init(\(params.compactMap { $0.effectiveName ?? "_" }.joined(separator: ":")))" var funcDecl = ImportedFunc( - parentName: currentType.name, + parentName: currentType.translatedType, identifier: initIdentifier, - returnType: currentType.name, + returnType: currentType.translatedType, parameters: params ) funcDecl.isInit = true @@ -213,7 +179,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 @@ -256,13 +222,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/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..1f6ddd20 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 ) } @@ -102,8 +102,21 @@ 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 { + if let (requiresArgument, _, hasCount) = name.isNameOfSwiftPointerType, !hasCount { // Dig out the pointee type if needed. if requiresArgument { guard let genericArguments else { @@ -121,7 +134,7 @@ extension Swift2JavaVisitor { cCompatibleConvention: .direct, originalSwiftType: type, cCompatibleSwiftType: "UnsafeMutableRawPointer", - cCompatibleJavaMemoryLayout: .memorySegment, + cCompatibleJavaMemoryLayout: .heapObject, javaType: .javaForeignMemorySegment ) } @@ -131,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: .memorySegment, - javaType: .class(package: nil, name: name) - ) + return importedNominal.translatedType } } @@ -194,7 +201,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 @@ -211,20 +218,56 @@ 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 } - } + +/// 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. case primitive(JavaType) - case memorySegment + + /// The Swift "Int" type, which may be either a Java int (32-bit platforms) or + /// Java long (64-bit platforms). + case int + + /// A Swift heap object, which is treated as a pointer for interoperability + /// purposes but must be retained/released to keep it alive. + case heapObject + + /// A C function pointer. In Swift, this will be a @convention(c) function. + /// In Java, a downcall handle to a function. + case cFunction +} + +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 { @@ -237,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/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 } } 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) {