From 4b96ba4b32adbf2162525af72b7f0d5899fce418 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 7 Jul 2025 10:08:18 -0700 Subject: [PATCH 1/2] [JExtract] Prepare Foundation Data --- .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 13 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 6 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 3 +- .../FFM/FFMSwift2JavaGenerator.swift | 2 - ...ISwift2JavaGenerator+JavaTranslation.swift | 8 +- .../Swift2JavaTranslator.swift | 30 ++- .../SwiftTypes/DependencyScanner.swift | 29 +++ .../SwiftTypes/SwiftKnownModules.swift | 92 ++++++++ .../SwiftTypes/SwiftKnownTypeDecls.swift | 61 ++++++ .../SwiftTypes/SwiftKnownTypes.swift | 46 ++-- .../SwiftNominalTypeDeclaration.swift | 19 +- .../SwiftParsedModuleSymbolTable.swift | 120 ----------- .../SwiftParsedModuleSymbolTableBuilder.swift | 175 ++++++++++++++++ .../SwiftStandardLibraryTypeDecls.swift | 198 ------------------ .../SwiftTypes/SwiftSymbolTable.swift | 125 +++++------ .../Asserts/LoweringAssertions.swift | 6 +- .../SwiftSymbolTableTests.swift | 8 +- 18 files changed, 479 insertions(+), 466 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift delete mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift delete mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index eb27e5f6..9f4a9ac9 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -102,7 +102,7 @@ enum CDeclToCLoweringError: Error { case invalidFunctionConvention(SwiftFunctionType) } -extension SwiftStandardLibraryTypeKind { +extension SwiftKnownTypeDeclKind { /// Determine the primitive C type that corresponds to this C standard /// library type, if there is one. var primitiveCType: CType? { @@ -125,7 +125,7 @@ extension SwiftStandardLibraryTypeKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index db764e2e..926f211e 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -29,7 +29,7 @@ extension FFMSwift2JavaGenerator { enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } /// Lower the given initializer to a C-compatible entrypoint, @@ -46,7 +46,7 @@ extension FFMSwift2JavaGenerator { symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } /// Lower the given variable decl to a C-compatible entrypoint, @@ -69,7 +69,7 @@ extension FFMSwift2JavaGenerator { enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } } @@ -77,8 +77,8 @@ extension FFMSwift2JavaGenerator { struct CdeclLowering { var knownTypes: SwiftKnownTypes - init(swiftStdlibTypes: SwiftStandardLibraryTypeDecls) { - self.knownTypes = SwiftKnownTypes(decls: swiftStdlibTypes) + init(symbolTable: SwiftSymbolTable) { + self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) } /// Lower the given Swift function signature to a Swift @_cdecl function signature, @@ -663,8 +663,7 @@ extension LoweredFunctionSignature { package func cdeclThunk( cName: String, swiftAPIName: String, - as apiKind: SwiftAPIKind, - stdlibTypes: SwiftStandardLibraryTypeDecls + as apiKind: SwiftAPIKind ) -> FunctionDeclSyntax { let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 16efddea..47e6b3eb 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes) + let translation = JavaTranslation(symbolTable: self.symbolTable) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -113,12 +113,12 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { - var swiftStdlibTypes: SwiftStandardLibraryTypeDecls + var symbolTable: SwiftSymbolTable func translate( _ decl: ImportedFunc ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) + let lowering = CdeclLowering(symbolTable: symbolTable) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index d6b09b24..862fa18e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -196,8 +196,7 @@ struct SwiftThunkTranslator { let thunkFunc = translated.loweredSignature.cdeclThunk( cName: thunkName, swiftAPIName: decl.name, - as: decl.apiKind, - stdlibTypes: st.swiftStdlibTypes + as: decl.apiKind ) return [DeclSyntax(thunkFunc)] } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 09fcf858..a709f5f7 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -24,7 +24,6 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String - let swiftStdlibTypes: SwiftStandardLibraryTypeDecls let symbolTable: SwiftSymbolTable var javaPackagePath: String { @@ -53,7 +52,6 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory self.symbolTable = translator.symbolTable - self.swiftStdlibTypes = translator.swiftStdlibTypeDecls // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a5df702c..929a332c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -67,7 +67,7 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { - guard let javaType = translate(standardLibraryType: knownType) else { + guard let javaType = translate(knownType: knownType) else { fatalError("unsupported known type: \(knownType)") } return javaType @@ -83,8 +83,8 @@ extension JNISwift2JavaGenerator { } } - func translate(standardLibraryType: SwiftStandardLibraryTypeKind) -> JavaType? { - switch standardLibraryType { + func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { + switch knownType { case .bool: .boolean case .int8: .byte case .uint16: .char @@ -101,6 +101,8 @@ extension JNISwift2JavaGenerator { .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: nil + case .data: + fatalError("unimplemented") } } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 89deda9a..a75ff54d 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -27,6 +27,9 @@ public final class Swift2JavaTranslator { let config: Configuration + /// The name of the Swift module being translated. + let swiftModuleName: String + // ==== Input struct Input { @@ -46,14 +49,7 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - package var swiftStdlibTypeDecls: SwiftStandardLibraryTypeDecls - - package let symbolTable: SwiftSymbolTable - - /// The name of the Swift module being translated. - var swiftModuleName: String { - symbolTable.moduleName - } + package var symbolTable: SwiftSymbolTable! = nil public init( config: Configuration @@ -62,12 +58,7 @@ public final class Swift2JavaTranslator { fatalError("Missing 'swiftModule' name.") // FIXME: can we make it required in config? but we shared config for many cases } self.config = config - self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModule) - - // Create a mock of the Swift standard library. - var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift") - self.swiftStdlibTypeDecls = SwiftStandardLibraryTypeDecls(into: &parsedSwiftModule) - self.symbolTable.importedModules.append(parsedSwiftModule.symbolTable) + self.swiftModuleName = swiftModule } } @@ -111,8 +102,11 @@ extension Swift2JavaTranslator { } package func prepareForTranslation() { - /// Setup the symbol table. - symbolTable.setup(inputs.map({ $0.syntax })) + self.symbolTable = SwiftSymbolTable.setup( + moduleName: self.swiftModuleName, + inputs.map({ $0.syntax }), + log: self.log + ) } } @@ -146,10 +140,10 @@ extension Swift2JavaTranslator { } // Whether to import this extension? - guard let nominalNode = symbolTable.parsedModule.nominalTypeSyntaxNodes[swiftNominalDecl] else { + guard swiftNominalDecl.moduleName == self.swiftModuleName else { return nil } - guard nominalNode.shouldImport(log: log) else { + guard swiftNominalDecl.syntax!.shouldImport(log: log) else { return nil } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift new file mode 100644 index 00000000..409c81a7 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Scan importing modules. +func importingModuleNames(sourceFile: SourceFileSyntax) -> [String] { + var importingModuleNames: [String] = [] + for item in sourceFile.statements { + if let importDecl = item.item.as(ImportDeclSyntax.self) { + guard let moduleName = importDecl.path.first?.name.text else { + continue + } + importingModuleNames.append(moduleName) + } + } + return importingModuleNames +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift new file mode 100644 index 00000000..f9b61b45 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax +import SwiftSyntaxBuilder + +enum SwiftKnownModule: String { + case swift = "Swift" + case foundation = "Foundation" + + var name: String { + return self.rawValue + } + + var symbolTable: SwiftModuleSymbolTable { + return switch self { + case .swift: swiftSymbolTable + case .foundation: foundationSymbolTable + } + } + + var sourceFile: SourceFileSyntax { + return switch self { + case .swift: swiftSourceFile + case .foundation: foundationSourceFile + } + } +} + +private var swiftSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Swift", importedModules: [:]) + builder.handle(sourceFile: swiftSourceFile) + return builder.finalize() +} + +private var foundationSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Foundation", importedModules: ["Swift": swiftSymbolTable]) + builder.handle(sourceFile: foundationSourceFile) + return builder.finalize() +} + +private let swiftSourceFile: SourceFileSyntax = """ + public struct Bool {} + public struct Int {} + public struct UInt {} + public struct Int8 {} + public struct UInt8 {} + public struct Int16 {} + public struct UInt16 {} + public struct Int32 {} + public struct UInt32 {} + public struct Int64 {} + public struct UInt64 {} + public struct Float {} + public struct Double {} + + public struct UnsafeRawPointer {} + public struct UnsafeMutableRawPointer {} + public struct UnsafeRawBufferPointer {} + public struct UnsafeMutableRawBufferPointer {} + + public struct UnsafePointer {} + public struct UnsafeMutablePointer {} + + public struct UnsafeBufferPointer {} + public struct UnsafeMutableBufferPointer {} + + public struct Void {} // FIXME: Support 'typealias Void = ()' + + public struct String { + public init(cString: UnsafePointer) + public func withCString(body: (UnsafePointer) -> Void) + } + """ + +private let foundationSourceFile: SourceFileSyntax = """ + public struct Data { + public init(bytes: UnsafeRawPointer, count: Int) + public func withUnsafeBytes(body: (UnsafeRawBufferPointer) -> Void) + } + """ diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift new file mode 100644 index 00000000..0ac915c7 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +enum SwiftKnownTypeDeclKind: String, Hashable { + case bool = "Swift.Bool" + case int = "Swift.Int" + case uint = "Swift.UInt" + case int8 = "Swift.Int8" + case uint8 = "Swift.UInt8" + case int16 = "Swift.Int16" + case uint16 = "Swift.UInt16" + case int32 = "Swift.Int32" + case uint32 = "Swift.UInt32" + case int64 = "Swift.Int64" + case uint64 = "Swift.UInt64" + case float = "Swift.Float" + case double = "Swift.Double" + case unsafeRawPointer = "Swift.UnsafeRawPointer" + case unsafeMutableRawPointer = "Swift.UnsafeMutableRawPointer" + case unsafeRawBufferPointer = "Swift.UnsafeRawBufferPointer" + case unsafeMutableRawBufferPointer = "Swift.UnsafeMutableRawBufferPointer" + case unsafePointer = "Swift.UnsafePointer" + case unsafeMutablePointer = "Swift.UnsafeMutablePointer" + case unsafeBufferPointer = "Swift.UnsafeBufferPointer" + case unsafeMutableBufferPointer = "Swift.UnsafeMutableBufferPointer" + case void = "Swift.Void" + case string = "Swift.String" + + case data = "Foundation.Data" + + var moduleAndName: (module: String, name: String) { + let qualified = self.rawValue + let period = qualified.firstIndex(of: ".")! + return ( + module: String(qualified[.. SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafePointerDecl, + nominalTypeDecl: symbolTable[.unsafePointer], genericArguments: [pointeeType] ) ) @@ -47,7 +47,7 @@ struct SwiftKnownTypes { func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeMutablePointerDecl, + nominalTypeDecl: symbolTable[.unsafeMutablePointer], genericArguments: [pointeeType] ) ) @@ -56,7 +56,7 @@ struct SwiftKnownTypes { func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeBufferPointerDecl, + nominalTypeDecl: symbolTable[.unsafeBufferPointer], genericArguments: [elementType] ) ) @@ -65,7 +65,7 @@ struct SwiftKnownTypes { func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeMutableBufferPointerDecl, + nominalTypeDecl: symbolTable[.unsafeMutableBufferPointer], genericArguments: [elementType] ) ) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 29a287fe..b63f930c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -31,28 +31,28 @@ package class SwiftNominalTypeDeclaration { /// The syntax node this declaration is derived from. /// Can be `nil` if this is loaded from a .swiftmodule. - var syntax: NominalTypeDeclSyntaxNode? + let syntax: NominalTypeDeclSyntaxNode? /// The kind of nominal type. - var kind: Kind + let kind: Kind /// The parent nominal type when this nominal type is nested inside another type, e.g., /// MyCollection.Iterator. - var parent: SwiftNominalTypeDeclaration? + let parent: SwiftNominalTypeDeclaration? /// The module in which this nominal type is defined. If this is a nested type, the /// module might be different from that of the parent type, if this nominal type /// is defined in an extension within another module. - var moduleName: String + let moduleName: String /// The name of this nominal type, e.g., 'MyCollection'. - var name: String + let name: String // TODO: Generic parameters. /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - lazy var knownStandardLibraryType: SwiftStandardLibraryTypeKind? = { + lazy var knownStandardLibraryType: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() @@ -66,6 +66,7 @@ package class SwiftNominalTypeDeclaration { self.moduleName = moduleName self.parent = parent self.name = node.name.text + self.syntax = node // Determine the kind from the syntax node. switch Syntax(node).as(SyntaxEnum.self) { @@ -80,12 +81,12 @@ package class SwiftNominalTypeDeclaration { /// Determine the known standard library type for this nominal type /// declaration. - private func computeKnownStandardLibraryType() -> SwiftStandardLibraryTypeKind? { - if parent != nil || moduleName != "Swift" { + private func computeKnownStandardLibraryType() -> SwiftKnownTypeDeclKind? { + if parent != nil { return nil } - return SwiftStandardLibraryTypeKind(typeNameInSwiftModule: name) + return SwiftKnownTypeDeclKind(rawValue: "\(moduleName).\(name)") } package var qualifiedName: String { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift deleted file mode 100644 index 1eb37eac..00000000 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift +++ /dev/null @@ -1,120 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -struct SwiftParsedModuleSymbolTable { - var symbolTable: SwiftModuleSymbolTable - - /// The nominal type declarations, indexed by the nominal type declaration syntax node. - var nominalTypeDeclarations: [SyntaxIdentifier: SwiftNominalTypeDeclaration] = [:] - - /// Mapping from the nominal type declarations in this module back to the syntax - /// node. This is the reverse mapping of 'nominalTypeDeclarations'. - var nominalTypeSyntaxNodes: [SwiftNominalTypeDeclaration: NominalTypeDeclSyntaxNode] = [:] - - init(moduleName: String) { - symbolTable = .init(moduleName: moduleName) - } -} - -extension SwiftParsedModuleSymbolTable: SwiftSymbolTableProtocol { - var moduleName: String { - symbolTable.moduleName - } - - func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { - symbolTable.lookupTopLevelNominalType(name) - } - - func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { - symbolTable.lookupNestedType(name, parent: parent) - } -} - -extension SwiftParsedModuleSymbolTable { - /// Look up a nominal type declaration based on its syntax node. - func lookup(_ node: NominalTypeDeclSyntaxNode) -> SwiftNominalTypeDeclaration? { - nominalTypeDeclarations[node.id] - } - - /// Add a nominal type declaration and all of the nested types within it to the symbol - /// table. - @discardableResult - mutating func addNominalTypeDeclaration( - _ node: NominalTypeDeclSyntaxNode, - parent: SwiftNominalTypeDeclaration? - ) -> SwiftNominalTypeDeclaration { - // If we have already recorded this nominal type declaration, we're done. - if let existingNominal = nominalTypeDeclarations[node.id] { - return existingNominal - } - - // Otherwise, create the nominal type declaration. - let nominalTypeDecl = SwiftNominalTypeDeclaration( - moduleName: moduleName, - parent: parent, - node: node - ) - - // Ensure that we can find this nominal type declaration again based on the syntax - // node, and vice versa. - nominalTypeDeclarations[node.id] = nominalTypeDecl - nominalTypeSyntaxNodes[nominalTypeDecl] = node - - if let parent { - // For nested types, make them discoverable from the parent type. - symbolTable.nestedTypes[parent, default: [:]][node.name.text] = nominalTypeDecl - } else { - // For top-level types, make them discoverable by name. - symbolTable.topLevelTypes[node.name.text] = nominalTypeDecl - } - - // Find any nested types within this nominal type and add them. - for member in node.memberBlock.members { - if let nominalMember = member.decl.asNominal { - addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl) - } - } - - return nominalTypeDecl - } - - /// Add any nested types within the given extension (with known extended nominal type - /// declaration) to the symbol table. - mutating func addExtension( - _ extensionNode: ExtensionDeclSyntax, - extending nominalTypeDecl: SwiftNominalTypeDeclaration - ) { - // Find any nested types within this extension and add them. - for member in extensionNode.memberBlock.members { - if let nominalMember = member.decl.asNominal { - addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl) - } - } - } -} - -extension DeclSyntaxProtocol { - var asNominal: NominalTypeDeclSyntaxNode? { - switch DeclSyntax(self).as(DeclSyntaxEnum.self) { - case .actorDecl(let actorDecl): actorDecl - case .classDecl(let classDecl): classDecl - case .enumDecl(let enumDecl): enumDecl - case .protocolDecl(let protocolDecl): protocolDecl - case .structDecl(let structDecl): structDecl - default: nil - } - } -} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift new file mode 100644 index 00000000..df266ff0 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -0,0 +1,175 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +struct SwiftParsedModuleSymbolTableBuilder { + /// The symbol table being built. + var symbolTable: SwiftModuleSymbolTable + + /// Imported modules to resolve type syntax. + let importedModules: [String: SwiftModuleSymbolTable] + + /// Extension decls their extended type hasn't been resolved. + var unresolvedExtensions: [ExtensionDeclSyntax] + + init(moduleName: String, importedModules: [String: SwiftModuleSymbolTable]) { + self.symbolTable = .init(moduleName: moduleName) + self.importedModules = importedModules + self.unresolvedExtensions = [] + } + + var moduleName: String { + symbolTable.moduleName + } +} + +extension SwiftParsedModuleSymbolTableBuilder { + + mutating func handle( + sourceFile: SourceFileSyntax + ) { + // Find top-level type declarations. + for statement in sourceFile.statements { + // We only care about declarations. + guard case .decl(let decl) = statement.item else { + continue + } + + if let nominalTypeNode = decl.asNominal { + self.handle(nominalTypeDecl: nominalTypeNode, parent: nil) + } + if let extensionNode = decl.as(ExtensionDeclSyntax.self) { + self.handle(extensionDecl: extensionNode) + } + } + } + + /// Add a nominal type declaration and all of the nested types within it to the symbol + /// table. + mutating func handle( + nominalTypeDecl node: NominalTypeDeclSyntaxNode, + parent: SwiftNominalTypeDeclaration? + ) { + // If we have already recorded a nominal type with the name in this module, + // it's an invalid redeclaration. + if let _ = symbolTable.lookupType(node.name.text, parent: parent) { + // TODO: Diagnose? + return + } + + // Otherwise, create the nominal type declaration. + let nominalTypeDecl = SwiftNominalTypeDeclaration( + moduleName: moduleName, + parent: parent, + node: node + ) + + if let parent { + // For nested types, make them discoverable from the parent type. + symbolTable.nestedTypes[parent, default: [:]][nominalTypeDecl.name] = nominalTypeDecl + } else { + // For top-level types, make them discoverable by name. + symbolTable.topLevelTypes[nominalTypeDecl.name] = nominalTypeDecl + } + + self.handle(memberBlock: node.memberBlock, parent: nominalTypeDecl) + } + + mutating func handle( + memberBlock node: MemberBlockSyntax, + parent: SwiftNominalTypeDeclaration + ) { + for member in node.members { + // Find any nested types within this nominal type and add them. + if let nominalMember = member.decl.asNominal { + self.handle(nominalTypeDecl: nominalMember, parent: parent) + } + } + + } + + mutating func handle( + extensionDecl node: ExtensionDeclSyntax + ) { + if !self.tryHandle(extension: node) { + self.unresolvedExtensions.append(node) + } + } + + /// Add any nested types within the given extension to the symbol table. + /// If the extended nominal type can't be resolved, returns false. + mutating func tryHandle( + extension node: ExtensionDeclSyntax + ) -> Bool { + // Try to resolve the type referenced by this extension declaration. + // If it fails, we'll try again later. + let table = SwiftSymbolTable( + parsedModule: symbolTable, + importedModules: importedModules + ) + guard let extendedType = try? SwiftType(node.extendedType, symbolTable: table) else { + return false + } + guard let extendedNominal = extendedType.asNominalTypeDeclaration else { + // Extending type was not a nominal type. Ignore it. + return true + } + + // Find any nested types within this extension and add them. + self.handle(memberBlock: node.memberBlock, parent: extendedNominal) + return true + } + + /// Finalize the symbol table and return it. + mutating func finalize() -> SwiftModuleSymbolTable { + // Handle the unresolved extensions. + // The work queue is required because, the extending type might be declared + // in another extension that hasn't been processed. E.g.: + // + // extension Outer.Inner { struct Deeper {} } + // extension Outer { struct Inner {} } + // struct Outer {} + // + while !unresolvedExtensions.isEmpty { + var extensions = self.unresolvedExtensions + extensions.removeAll(where: { + self.tryHandle(extension: $0) + }) + + // If we didn't resolve anything, we're done. + if extensions.count == unresolvedExtensions.count { + break + } + + assert(extensions.count < unresolvedExtensions.count) + self.unresolvedExtensions = extensions + } + + return symbolTable + } +} + +extension DeclSyntaxProtocol { + var asNominal: NominalTypeDeclSyntaxNode? { + switch DeclSyntax(self).as(DeclSyntaxEnum.self) { + case .actorDecl(let actorDecl): actorDecl + case .classDecl(let classDecl): classDecl + case .enumDecl(let enumDecl): enumDecl + case .protocolDecl(let protocolDecl): protocolDecl + case .structDecl(let structDecl): structDecl + default: nil + } + } +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift deleted file mode 100644 index 5678b2bb..00000000 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift +++ /dev/null @@ -1,198 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable { - case bool = "Bool" - case int = "Int" - case uint = "UInt" - case int8 = "Int8" - case uint8 = "UInt8" - case int16 = "Int16" - case uint16 = "UInt16" - case int32 = "Int32" - case uint32 = "UInt32" - case int64 = "Int64" - case uint64 = "UInt64" - case float = "Float" - case double = "Double" - case unsafeRawPointer = "UnsafeRawPointer" - case unsafeMutableRawPointer = "UnsafeMutableRawPointer" - case unsafeRawBufferPointer = "UnsafeRawBufferPointer" - case unsafeMutableRawBufferPointer = "UnsafeMutableRawBufferPointer" - case unsafePointer = "UnsafePointer" - case unsafeMutablePointer = "UnsafeMutablePointer" - case unsafeBufferPointer = "UnsafeBufferPointer" - case unsafeMutableBufferPointer = "UnsafeMutableBufferPointer" - case string = "String" - case void = "Void" - - var typeName: String { rawValue } - - init?(typeNameInSwiftModule: String) { - self.init(rawValue: typeNameInSwiftModule) - } - - /// Whether this declaration is generic. - var isGeneric: Bool { - switch self { - case .bool, .double, .float, .int, .int8, .int16, .int32, .int64, - .uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer, - .unsafeMutableRawPointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .string, .void: - false - - case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, - .unsafeMutableBufferPointer: - true - } - } - - var isPointer: Bool { - switch self { - case .unsafePointer, .unsafeMutablePointer, .unsafeRawPointer, .unsafeMutableRawPointer: - return true - default: - return false - } - } -} - -/// Captures many types from the Swift standard library in their most basic -/// forms, so that the translator can reason about them in source code. -public struct SwiftStandardLibraryTypeDecls { - // Swift.UnsafePointer - let unsafePointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeMutablePointer - let unsafeMutablePointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeBufferPointer - let unsafeBufferPointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeMutableBufferPointer - let unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration - - /// Mapping from known standard library types to their nominal type declaration. - let knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] - - /// Mapping from nominal type declarations to known types. - let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] - - private static func recordKnownType( - _ type: SwiftStandardLibraryTypeKind, - _ syntax: NominalTypeDeclSyntaxNode, - knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], - parsedModule: inout SwiftParsedModuleSymbolTable - ) { - let nominalDecl = parsedModule.addNominalTypeDeclaration(syntax, parent: nil) - knownTypeToNominal[type] = nominalDecl - nominalTypeDeclToKnownType[nominalDecl] = type - } - - private static func recordKnownNonGenericStruct( - _ type: SwiftStandardLibraryTypeKind, - knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], - parsedModule: inout SwiftParsedModuleSymbolTable - ) { - recordKnownType( - type, - StructDeclSyntax( - name: .identifier(type.typeName), - memberBlock: .init(members: []) - ), - knownTypeToNominal: &knownTypeToNominal, - nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, - parsedModule: &parsedModule - ) - } - - init(into parsedModule: inout SwiftParsedModuleSymbolTable) { - // Pointer types - self.unsafePointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafePointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - self.unsafeMutablePointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafeMutablePointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - self.unsafeBufferPointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafeBufferPointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - self.unsafeMutableBufferPointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafeMutableBufferPointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - var knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] = [:] - var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] = [:] - - // Handle all of the non-generic types at once. - for knownType in SwiftStandardLibraryTypeKind.allCases { - guard !knownType.isGeneric else { - continue - } - - Self.recordKnownNonGenericStruct( - knownType, - knownTypeToNominal: &knownTypeToNominal, - nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, - parsedModule: &parsedModule - ) - } - - self.knownTypeToNominal = knownTypeToNominal - self.nominalTypeDeclToKnownType = nominalTypeDeclToKnownType - } - - subscript(knownType: SwiftStandardLibraryTypeKind) -> SwiftNominalTypeDeclaration { - knownTypeToNominal[knownType]! - } - - subscript(nominalType: SwiftNominalTypeDeclaration) -> SwiftStandardLibraryTypeKind? { - nominalTypeDeclToKnownType[nominalType] - } -} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 87c2c80f..0ff9653e 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -38,89 +38,50 @@ extension SwiftSymbolTableProtocol { } package class SwiftSymbolTable { - var importedModules: [SwiftModuleSymbolTable] = [] - var parsedModule: SwiftParsedModuleSymbolTable + let importedModules: [String: SwiftModuleSymbolTable] + let parsedModule:SwiftModuleSymbolTable - package init(parsedModuleName: String) { - self.parsedModule = SwiftParsedModuleSymbolTable(moduleName: parsedModuleName) - } + private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] - func addImportedModule(symbolTable: SwiftModuleSymbolTable) { - importedModules.append(symbolTable) + init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) { + self.parsedModule = parsedModule + self.importedModules = importedModules } } extension SwiftSymbolTable { - package func setup(_ sourceFiles: some Collection) { - // First, register top-level and nested nominal types to the symbol table. + package static func setup( + moduleName: String, + _ sourceFiles: some Collection, + log: Logger + ) -> SwiftSymbolTable { + + // Prepare imported modules. + // FIXME: Support arbitrary dependencies. + var moduleNames: Set = [] for sourceFile in sourceFiles { - self.addNominalTypeDeclarations(sourceFile) - } - - // Next bind the extensions. - - // The work queue is required because, the extending type might be declared - // in another extension that hasn't been processed. E.g.: - // - // extension Outer.Inner { struct Deeper {} } - // extension Outer { struct Inner {} } - // struct Outer {} - // - var unresolvedExtensions: [ExtensionDeclSyntax] = [] - for sourceFile in sourceFiles { - // Find extensions. - for statement in sourceFile.statements { - // We only care about extensions at top-level. - if case .decl(let decl) = statement.item, let extNode = decl.as(ExtensionDeclSyntax.self) { - let resolved = handleExtension(extNode) - if !resolved { - unresolvedExtensions.append(extNode) - } - } - } + moduleNames.formUnion(importingModuleNames(sourceFile: sourceFile)) } - - while !unresolvedExtensions.isEmpty { - let numExtensionsBefore = unresolvedExtensions.count - unresolvedExtensions.removeAll(where: handleExtension(_:)) - - // If we didn't resolve anything, we're done. - if numExtensionsBefore == unresolvedExtensions.count { - break + var importedModules: [String: SwiftModuleSymbolTable] = [:] + importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable + for moduleName in moduleNames.sorted() { + if + importedModules[moduleName] == nil, + let knownModule = SwiftKnownModule(rawValue: moduleName) + { + importedModules[moduleName] = knownModule.symbolTable } - assert(numExtensionsBefore > unresolvedExtensions.count) } - } - private func addNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) { - // Find top-level nominal type declarations. - for statement in sourceFile.statements { - // We only care about declarations. - guard case .decl(let decl) = statement.item, - let nominalTypeNode = decl.asNominal else { - continue - } - - parsedModule.addNominalTypeDeclaration(nominalTypeNode, parent: nil) - } - } + // FIXME: Support granular lookup context (file, type context). - private func handleExtension(_ extensionDecl: ExtensionDeclSyntax) -> Bool { - // Try to resolve the type referenced by this extension declaration. - // If it fails, we'll try again later. - guard let extendedType = try? SwiftType(extensionDecl.extendedType, symbolTable: self) else { - return false - } - guard let extendedNominal = extendedType.asNominalTypeDeclaration else { - // Extending type was not a nominal type. Ignore it. - return true + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: moduleName, importedModules: importedModules) + // First, register top-level and nested nominal types to the symbol table. + for sourceFile in sourceFiles { + builder.handle(sourceFile: sourceFile) } - - // Register nested nominals in extensions to the symbol table. - parsedModule.addExtension(extensionDecl, extending: extendedNominal) - - // We have successfully resolved the extended type. Record it. - return true + let parsedModule = builder.finalize() + return SwiftSymbolTable(parsedModule: parsedModule, importedModules: importedModules) } } @@ -134,12 +95,14 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules { + for importedModule in importedModules.values { if let result = importedModule.lookupTopLevelNominalType(name) { return result } } + // FIXME: Implement module qualified name lookups. E.g. 'Swift.String' + return nil } @@ -149,7 +112,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules { + for importedModule in importedModules.values { if let result = importedModule.lookupNestedType(name, parent: parent) { return result } @@ -158,3 +121,21 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return nil } } + +extension SwiftSymbolTable { + /// Map 'SwiftKnownTypeDeclKind' to the declaration. + subscript(knownType: SwiftKnownTypeDeclKind) -> SwiftNominalTypeDeclaration! { + if let known = knownTypeToNominal[knownType] { + return known + } + + let (module, name) = knownType.moduleAndName + guard let moduleTable = importedModules[module] else { + fatalError("module \(module) is not known") + } + + let found = moduleTable.lookupTopLevelNominalType(name) + knownTypeToNominal[knownType] = found + return found + } +} diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index 05323630..eebfdf4a 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -73,8 +73,7 @@ func assertLoweredFunction( let loweredCDecl = loweredFunction.cdeclThunk( cName: "c_\(swiftFunctionName)", swiftAPIName: swiftFunctionName, - as: apiKind, - stdlibTypes: translator.swiftStdlibTypeDecls + as: apiKind ) #expect( @@ -141,8 +140,7 @@ func assertLoweredVariableAccessor( let loweredCDecl = loweredFunction?.cdeclThunk( cName: "c_\(swiftVariableName)", swiftAPIName: swiftVariableName, - as: isSet ? .setter : .getter, - stdlibTypes: translator.swiftStdlibTypeDecls + as: isSet ? .setter : .getter ) #expect( diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index 2bbaa913..fdbf2d5f 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -21,7 +21,6 @@ import Testing struct SwiftSymbolTableSuite { @Test func lookupBindingTests() throws { - let symbolTable = SwiftSymbolTable(parsedModuleName: "MyModule") let sourceFile1: SourceFileSyntax = """ extension X.Y { struct Z { } @@ -33,8 +32,11 @@ struct SwiftSymbolTableSuite { let sourceFile2: SourceFileSyntax = """ struct X {} """ - - symbolTable.setup([sourceFile1, sourceFile2]) + let symbolTable = SwiftSymbolTable.setup( + moduleName: "MyModule", + [sourceFile1, sourceFile2], + log: Logger(label: "swift-java", logLevel: .critical) + ) let x = try #require(symbolTable.lookupType("X", parent: nil)) let xy = try #require(symbolTable.lookupType("Y", parent: x)) From 54c8a252f762ce9ff4cba1391545852d7196ab1a Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 7 Jul 2025 14:20:47 -0700 Subject: [PATCH 2/2] [JExtract] Import Data --- .../JExtractSwiftPlugin.swift | 7 + .../MySwiftLibrary/MySwiftLibrary.swift | 6 + .../com/example/swift/HelloJava2Swift.java | 20 ++ ...Swift2JavaGenerator+FunctionLowering.swift | 15 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 8 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 13 + .../Swift2JavaTranslator.swift | 55 ++++ .../SwiftTypes/SwiftKnownModules.swift | 7 +- .../SwiftTypes/SwiftSymbolTable.swift | 2 +- .../JExtractSwiftTests/DataImportTests.swift | 281 ++++++++++++++++++ 10 files changed, 406 insertions(+), 8 deletions(-) create mode 100644 Tests/JExtractSwiftTests/DataImportTests.swift diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 7bfb51cf..83c5f0d9 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -100,6 +100,13 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { outputSwiftDirectory.appending(path: "\(sourceModule.name)Module+SwiftJava.swift") ] + // If the module uses 'Data' type, the thunk file is emitted as if 'Data' is declared + // in that module. Declare the thunk file as the output. + // FIXME: Make this conditional. + outputSwiftFiles += [ + outputSwiftDirectory.appending(path: "Data+SwiftJava.swift") + ] + return [ .buildCommand( displayName: "Generate Java wrappers for Swift types", diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 18b6546d..ce949283 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -23,6 +23,8 @@ import Glibc import Darwin.C #endif +import Foundation + public func helloWorld() { p("\(#function)") } @@ -53,6 +55,10 @@ public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1)) +public func globalReceiveReturnData(data: Data) -> Data { + return Data(data) +} + public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index c12d82dd..d7265d12 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -21,6 +21,10 @@ import org.swift.swiftkit.SwiftArena; import org.swift.swiftkit.SwiftKit; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + public class HelloJava2Swift { public static void main(String[] args) { @@ -77,6 +81,22 @@ static void examples() { }); } + // Example of using 'Data'. + try (var arena = SwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var origDat = Data.init(origBytes, origBytes.byteSize(), arena); + + // var origBytes = arena.allocate(ValueLayout.JAVA_INT, arry.length); + // origBytes.copyFrom(MemorySegment.ofArray(arry)); + // var dat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); + // dat.withUnsafeBytes((retBytes) -> { + // SwiftKit.trace("retBytes=" + retBytes.toArray(ValueLayout.JAVA_INT)[1]); + // SwiftKit.trace("foobar"); + // }); + //SwiftKit.trace(origDat); + } + + System.out.println("DONE."); } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 926f211e..488bd4e7 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -266,6 +266,9 @@ struct CdeclLowering { ) } + case .data: + break + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) @@ -407,6 +410,9 @@ struct CdeclLowering { ]) ) + case .data: + break + default: throw LoweringError.unhandledType(type) } @@ -509,6 +515,9 @@ struct CdeclLowering { // Returning string is not supported at this point. throw LoweringError.unhandledType(type) + case .data: + break + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) @@ -764,7 +773,7 @@ extension LoweredFunctionSignature { } enum LoweringError: Error { - case inoutNotSupported(SwiftType) - case unhandledType(SwiftType) - case effectNotSupported(SwiftEffectSpecifier) + case inoutNotSupported(SwiftType, file: String = #file, line: Int = #line) + case unhandledType(SwiftType, file: String = #file, line: Int = #line) + case effectNotSupported(SwiftEffectSpecifier, file: String = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 47e6b3eb..b4f364a7 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -353,6 +353,9 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) ) + case .data: + break + default: throw JavaTranslationError.unhandledType(swiftType) } @@ -438,6 +441,9 @@ extension FFMSwift2JavaGenerator { ) ) + case .data: + break + case .unsafePointer, .unsafeMutablePointer: // FIXME: Implement throw JavaTranslationError.unhandledType(swiftType) @@ -629,6 +635,6 @@ extension CType { } enum JavaTranslationError: Error { - case inoutNotSupported(SwiftType) + case inoutNotSupported(SwiftType, file: String = #file, line: Int = #line) case unhandledType(SwiftType, file: String = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 862fa18e..d717255d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -87,6 +87,8 @@ extension FFMSwift2JavaGenerator { """) + printSwiftThunkImports(&printer) + for thunk in stt.renderGlobalThunks() { printer.print(thunk) printer.println() @@ -114,11 +116,22 @@ extension FFMSwift2JavaGenerator { """ ) + printSwiftThunkImports(&printer) + for thunk in stt.renderThunks(forType: ty) { printer.print("\(thunk)") printer.print("") } } + + func printSwiftThunkImports(_ printer: inout CodePrinter) { + for module in self.symbolTable.importedModules.keys.sorted() { + guard module != "Swift" else { + continue + } + printer.print("import \(module)") + } + } } struct SwiftThunkTranslator { diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index a75ff54d..3f704756 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -99,6 +99,14 @@ extension Swift2JavaTranslator { log.trace("Analyzing \(input.filePath)") visitor.visit(sourceFile: input.syntax) } + + // If any API uses 'Foundation.Data', import 'Data' as if it's declared in + // this module. + if let dataDecl = self.symbolTable[.data] { + if self.isUsing(dataDecl) { + visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) + } + } } package func prepareForTranslation() { @@ -108,6 +116,53 @@ extension Swift2JavaTranslator { log: self.log ) } + + // Check if any of the imported decls uses the specified nominal declaration. + func isUsing(_ decl: SwiftNominalTypeDeclaration) -> Bool { + func check(_ type: SwiftType) -> Bool { + switch type { + case .nominal(let nominal): + return nominal.nominalTypeDecl == decl + case .optional(let ty): + return check(ty) + case .tuple(let tuple): + return tuple.contains(where: check) + case .function(let fn): + return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) }) + case .metatype(let ty): + return check(ty) + } + } + + func check(_ fn: ImportedFunc) -> Bool { + if check(fn.functionSignature.result.type) { + return true + } + if fn.functionSignature.parameters.contains(where: { check($0.type) }) { + return true + } + return false + } + + if self.importedGlobalFuncs.contains(where: check) { + return true + } + if self.importedGlobalVariables.contains(where: check) { + return true + } + for importedType in self.importedTypes.values { + if importedType.initializers.contains(where: check) { + return true + } + if importedType.methods.contains(where: check) { + return true + } + if importedGlobalVariables.contains(where: check) { + return true + } + } + return false + } } // ==== ---------------------------------------------------------------------------------------------------------------- diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index f9b61b45..2a5c0cfc 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -76,17 +76,18 @@ private let swiftSourceFile: SourceFileSyntax = """ public struct UnsafeBufferPointer {} public struct UnsafeMutableBufferPointer {} - public struct Void {} // FIXME: Support 'typealias Void = ()' + // FIXME: Support 'typealias Void = ()' + public struct Void {} public struct String { public init(cString: UnsafePointer) - public func withCString(body: (UnsafePointer) -> Void) + public func withCString(_ body: (UnsafePointer) -> Void) } """ private let foundationSourceFile: SourceFileSyntax = """ public struct Data { public init(bytes: UnsafeRawPointer, count: Int) - public func withUnsafeBytes(body: (UnsafeRawBufferPointer) -> Void) + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) } """ diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 0ff9653e..422fa11d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -131,7 +131,7 @@ extension SwiftSymbolTable { let (module, name) = knownType.moduleAndName guard let moduleTable = importedModules[module] else { - fatalError("module \(module) is not known") + return nil } let found = moduleTable.lookupTopLevelNominalType(name) diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift new file mode 100644 index 00000000..efcfa835 --- /dev/null +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -0,0 +1,281 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +final class DataImportTests { + let class_interfaceFile = + """ + import Foundation + + public func receiveData(dat: Data) + public func returnData() -> Data + """ + + @Test("Import Data: Swift thunks") + func swiftThunk() throws { + + try assertOutput( + input: class_interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + import Foundation + """, + """ + @_cdecl("swiftjava_SwiftModule_receiveData_dat") + public func swiftjava_SwiftModule_receiveData_dat(_ dat: UnsafeRawPointer) { + receiveData(dat: dat.assumingMemoryBound(to: Data.self).pointee) + } + """, + """ + @_cdecl("swiftjava_SwiftModule_returnData") + public func swiftjava_SwiftModule_returnData(_ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Data.self).initialize(to: returnData()) + } + """, + + """ + @_cdecl("swiftjava_getType_SwiftModule_Data") + public func swiftjava_getType_SwiftModule_Data() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(Data.self, to: UnsafeMutableRawPointer.self) + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_init_bytes_count") + public func swiftjava_SwiftModule_Data_init_bytes_count(_ bytes: UnsafeRawPointer, _ count: Int, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Data.self).initialize(to: Data(bytes: bytes, count: count)) + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_withUnsafeBytes_body") + public func swiftjava_SwiftModule_Data_withUnsafeBytes_body(_ body: @convention(c) (UnsafeRawPointer?, Int) -> Void, _ self: UnsafeRawPointer) { + self.assumingMemoryBound(to: Data.self).pointee.withUnsafeBytes(body: { (_0) in + return body(_0.baseAddress, _0.count) + }) + } + """, + ] + ) + } + + @Test("Import Data: JavaBindings") + func javaBindings() throws { + + try assertOutput( + input: class_interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveData_dat(const void *dat) + * } + */ + private static class swiftjava_SwiftModule_receiveData_dat { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* dat: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveData_dat"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment dat) { + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(dat); + } + HANDLE.invokeExact(dat); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveData(dat: Data) + * } + */ + public static void receiveData(Data dat) { + swiftjava_SwiftModule_receiveData_dat.call(dat.$memorySegment()); + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_returnData(void *_result) + * } + */ + private static class swiftjava_SwiftModule_returnData { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _result: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_returnData"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment _result) { + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(_result); + } + HANDLE.invokeExact(_result); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnData() -> Data + * } + */ + public static Data returnData(SwiftArena swiftArena$) { + MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); + swiftjava_SwiftModule_returnData.call(_result); + return new Data(_result, swiftArena$); + } + """, + + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_Data_init_bytes_count(const void *bytes, ptrdiff_t count, void *_result) + * } + */ + private static class swiftjava_SwiftModule_Data_init_bytes_count { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* bytes: */SwiftValueLayout.SWIFT_POINTER, + /* count: */SwiftValueLayout.SWIFT_INT, + /* _result: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_init_bytes_count"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment bytes, long count, java.lang.foreign.MemorySegment _result) { + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(bytes, count, _result); + } + HANDLE.invokeExact(bytes, count, _result); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(bytes: UnsafeRawPointer, count: Int) + * } + */ + public static Data init(java.lang.foreign.MemorySegment bytes, long count, SwiftArena swiftArena$) { + MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); + swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result); + return new Data(_result, swiftArena$); + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_Data_withUnsafeBytes_body(void (*body)(const void *, ptrdiff_t), const void *self) + * } + */ + private static class swiftjava_SwiftModule_Data_withUnsafeBytes_body { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* body: */SwiftValueLayout.SWIFT_POINTER, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_withUnsafeBytes_body"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment body, java.lang.foreign.MemorySegment self) { + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(body, self); + } + HANDLE.invokeExact(body, self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * {snippet lang=c : + * void (*)(const void *, ptrdiff_t) + * } + */ + private static class $body { + @FunctionalInterface + public interface Function { + void apply(java.lang.foreign.MemorySegment _0, long _1); + } + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _0: */SwiftValueLayout.SWIFT_POINTER, + /* _1: */SwiftValueLayout.SWIFT_INT + ); + private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + } + """, + + """ + public static class withUnsafeBytes { + @FunctionalInterface + public interface body { + void apply(java.lang.foreign.MemorySegment _0); + } + private static MemorySegment $toUpcallStub(body fi, Arena arena) { + return swiftjava_SwiftModule_Data_withUnsafeBytes_body.$body.toUpcallStub((_0_pointer, _0_count) -> { + fi.apply(_0_pointer.reinterpret(_0_count)); + }, arena); + } + } + """, + + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func withUnsafeBytes(body: (UnsafeRawBufferPointer) -> Void) + * } + */ + public void withUnsafeBytes(withUnsafeBytes.body body) { + $ensureAlive(); + try(var arena$ = Arena.ofConfined()) { + swiftjava_SwiftModule_Data_withUnsafeBytes_body.call(withUnsafeBytes.$toUpcallStub(body, arena$), this.$memorySegment()); + } + } + """ + ] + ) + } + +}