From 1e458ca9d2166ac9036b2b8b8275a7f09cabf2f2 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Wed, 30 Apr 2025 16:31:43 -0700 Subject: [PATCH] [jextract] Remove NominalTypeResolution Instead, use SwiftSymbolTable mechanism. --- Sources/JExtractSwift/ImportedDecls.swift | 20 +- .../JExtractSwift/NominalTypeResolution.swift | 354 ------------------ .../Swift2JavaTranslator+Printing.swift | 10 +- .../JExtractSwift/Swift2JavaTranslator.swift | 70 ++-- Sources/JExtractSwift/Swift2JavaVisitor.swift | 10 +- Sources/JExtractSwift/SwiftKit+Printing.swift | 4 +- .../JExtractSwift/SwiftThunkTranslator.swift | 2 +- .../SwiftNominalTypeDeclaration.swift | 18 +- .../SwiftTypes/SwiftSymbolTable.swift | 89 +++-- Sources/JExtractSwift/TranslatedType.swift | 7 +- .../NominalTypeResolutionTests.swift | 64 ---- .../SwiftSymbolTableTests.swift | 47 +++ 12 files changed, 192 insertions(+), 503 deletions(-) delete mode 100644 Sources/JExtractSwift/NominalTypeResolution.swift delete mode 100644 Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift create mode 100644 Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index c246dcbc..64693967 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -25,17 +25,17 @@ public typealias JavaPackage = 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 let swiftTypeName: String - public let javaType: JavaType - public var kind: NominalTypeKind +package struct ImportedNominalType: ImportedDecl { + let swiftNominal: SwiftNominalTypeDeclaration + let javaType: JavaType + var kind: NominalTypeKind - public var initializers: [ImportedFunc] = [] - public var methods: [ImportedFunc] = [] - public var variables: [ImportedVariable] = [] + package var initializers: [ImportedFunc] = [] + package var methods: [ImportedFunc] = [] + package var variables: [ImportedVariable] = [] - public init(swiftTypeName: String, javaType: JavaType, kind: NominalTypeKind) { - self.swiftTypeName = swiftTypeName + init(swiftNominal: SwiftNominalTypeDeclaration, javaType: JavaType, kind: NominalTypeKind) { + self.swiftNominal = swiftNominal self.javaType = javaType self.kind = kind } @@ -43,7 +43,7 @@ public struct ImportedNominalType: ImportedDecl { var translatedType: TranslatedType { TranslatedType( cCompatibleConvention: .direct, - originalSwiftType: "\(raw: swiftTypeName)", + originalSwiftType: "\(raw: swiftNominal.qualifiedName)", originalSwiftTypeKind: self.kind, cCompatibleSwiftType: "UnsafeRawPointer", cCompatibleJavaMemoryLayout: .heapObject, diff --git a/Sources/JExtractSwift/NominalTypeResolution.swift b/Sources/JExtractSwift/NominalTypeResolution.swift deleted file mode 100644 index b1185cde..00000000 --- a/Sources/JExtractSwift/NominalTypeResolution.swift +++ /dev/null @@ -1,354 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 SwiftSyntax - -/// Perform nominal type resolution, including the binding of extensions to -/// their extended nominal types and mapping type names to their full names. -@_spi(Testing) -public class NominalTypeResolution { - /// Mapping from the syntax identifier for a given type declaration node, - /// such as StructDeclSyntax, to the set of extensions of this particular - /// type. - private var extensionsByType: [SyntaxIdentifier: [ExtensionDeclSyntax]] = [:] - - /// Mapping from extension declarations to the type declaration that they - /// extend. - var resolvedExtensions: [ExtensionDeclSyntax: NominalTypeDeclSyntaxNode] = [:] - - /// Extensions that have been encountered but not yet resolved to - private var unresolvedExtensions: [ExtensionDeclSyntax] = [] - - /// Mapping from qualified nominal type names to their syntax nodes. - var topLevelNominalTypes: [String: NominalTypeDeclSyntaxNode] = [:] - - @_spi(Testing) public init() { } -} - -/// A syntax node for a nominal type declaration. -@_spi(Testing) -public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax - -// MARK: Nominal type name resolution. -extension NominalTypeResolution { - /// Compute the fully-qualified name of the given nominal type node. - /// - /// This produces the name that can be resolved back to the nominal type - /// via resolveNominalType(_:). - @_spi(Testing) - public func fullyQualifiedName(of node: NominalTypeDeclSyntaxNode) -> String? { - let nameComponents = fullyQualifiedNameComponents(of: node) - return nameComponents.isEmpty ? nil : nameComponents.joined(separator: ".") - } - - private func fullyQualifiedNameComponents(of node: NominalTypeDeclSyntaxNode) -> [String] { - var nameComponents: [String] = [] - - var currentNode = Syntax(node) - while true { - // If it's a nominal type, add its name. - if let nominal = currentNode.asProtocol(SyntaxProtocol.self) as? NominalTypeDeclSyntaxNode, - let nominalName = nominal.name.identifier?.name { - nameComponents.append(nominalName) - } - - // If it's an extension, add the full name of the extended type. - if let extensionDecl = currentNode.as(ExtensionDeclSyntax.self), - let extendedNominal = extendedType(of: extensionDecl) { - let extendedNominalNameComponents = fullyQualifiedNameComponents(of: extendedNominal) - return extendedNominalNameComponents + nameComponents.reversed() - } - - guard let parent = currentNode.parent else { - break - - } - currentNode = parent - } - - return nameComponents.reversed() - } - - /// Resolve a nominal type name to its syntax node, or nil if it cannot be - /// resolved for any reason. - @_spi(Testing) - public func resolveNominalType(_ name: String) -> NominalTypeDeclSyntaxNode? { - let components = name.split(separator: ".") - return resolveNominalType(components) - } - - /// Resolve a nominal type name to its syntax node, or nil if it cannot be - /// resolved for any reason. - private func resolveNominalType(_ nameComponents: some Sequence) -> NominalTypeDeclSyntaxNode? { - // Resolve the name components in order. - var currentNode: NominalTypeDeclSyntaxNode? = nil - for nameComponentStr in nameComponents { - let nameComponent = String(nameComponentStr) - - var nextNode: NominalTypeDeclSyntaxNode? = nil - if let currentNode { - nextNode = lookupNominalType(nameComponent, in: currentNode) - } else { - nextNode = topLevelNominalTypes[nameComponent] - } - - // If we couldn't resolve the next name, we're done. - guard let nextNode else { - return nil - } - - currentNode = nextNode - } - - return currentNode - } - - /// Look for a nominal type with the given name within this declaration group, - /// which could be a nominal type declaration or extension thereof. - private func lookupNominalType( - _ name: String, - inDeclGroup parentNode: some DeclGroupSyntax - ) -> NominalTypeDeclSyntaxNode? { - for member in parentNode.memberBlock.members { - let memberDecl = member.decl.asProtocol(DeclSyntaxProtocol.self) - - // If we have a member with the given name that is a nominal type - // declaration, we found what we're looking for. - if let namedMemberDecl = memberDecl.asProtocol(NamedDeclSyntax.self), - namedMemberDecl.name.identifier?.name == name, - let nominalTypeDecl = memberDecl as? NominalTypeDeclSyntaxNode - { - return nominalTypeDecl - } - } - - return nil - } - - /// Lookup nominal type name within a given nominal type. - private func lookupNominalType( - _ name: String, - in parentNode: NominalTypeDeclSyntaxNode - ) -> NominalTypeDeclSyntaxNode? { - // Look in the parent node itself. - if let found = lookupNominalType(name, inDeclGroup: parentNode) { - return found - } - - // Look in known extensions of the parent node. - if let extensions = extensionsByType[parentNode.id] { - for extensionDecl in extensions { - if let found = lookupNominalType(name, inDeclGroup: extensionDecl) { - return found - } - } - } - - return nil - } -} - -// MARK: Binding extensions -extension NominalTypeResolution { - /// Look up the nominal type declaration to which this extension is bound. - @_spi(Testing) - public func extendedType(of extensionDecl: ExtensionDeclSyntax) -> NominalTypeDeclSyntaxNode? { - return resolvedExtensions[extensionDecl] - } - - /// Bind all of the unresolved extensions to their nominal types. - /// - /// Returns the list of extensions that could not be resolved. - @_spi(Testing) - @discardableResult - public func bindExtensions() -> [ExtensionDeclSyntax] { - while !unresolvedExtensions.isEmpty { - // Try to resolve all of the unresolved extensions. - let numExtensionsBefore = unresolvedExtensions.count - unresolvedExtensions.removeAll { extensionDecl in - // Try to resolve the type referenced by this extension declaration. If - // it fails, we'll try again later. - let nestedTypeNameComponents = extensionDecl.nestedTypeName - guard let resolvedType = resolveNominalType(nestedTypeNameComponents) else { - return false - } - - // We have successfully resolved the extended type. Record it and - // remove the extension from the list of unresolved extensions. - extensionsByType[resolvedType.id, default: []].append(extensionDecl) - resolvedExtensions[extensionDecl] = resolvedType - - return true - } - - // If we didn't resolve anything, we're done. - if numExtensionsBefore == unresolvedExtensions.count { - break - } - - assert(numExtensionsBefore > unresolvedExtensions.count) - } - - // Any unresolved extensions at this point are fundamentally unresolvable. - return unresolvedExtensions - } -} - -extension ExtensionDeclSyntax { - /// Produce the nested type name for the given decl - fileprivate var nestedTypeName: [String] { - var nameComponents: [String] = [] - var extendedType = extendedType - while true { - switch extendedType.as(TypeSyntaxEnum.self) { - case .attributedType(let attributedType): - extendedType = attributedType.baseType - continue - - case .identifierType(let identifierType): - guard let identifier = identifierType.name.identifier else { - return [] - } - - nameComponents.append(identifier.name) - return nameComponents.reversed() - - case .memberType(let memberType): - guard let identifier = memberType.name.identifier else { - return [] - } - - nameComponents.append(identifier.name) - extendedType = memberType.baseType - continue - - // Structural types implemented as nominal types. - case .arrayType: - return ["Array"] - - case .dictionaryType: - return ["Dictionary"] - - case .implicitlyUnwrappedOptionalType, .optionalType: - return [ "Optional" ] - - // Types that never involve nominals. - - case .classRestrictionType, .compositionType, .functionType, .metatypeType, - .missingType, .namedOpaqueReturnType, .packElementType, - .packExpansionType, .someOrAnyType, .suppressedType, .tupleType: - return [] - } - } - } -} - -// MARK: Adding source files to the resolution. -extension NominalTypeResolution { - /// Add the given source file. - @_spi(Testing) - public func addSourceFile(_ sourceFile: SourceFileSyntax) { - let visitor = NominalAndExtensionFinder(typeResolution: self) - visitor.walk(sourceFile) - } - - private class NominalAndExtensionFinder: SyntaxVisitor { - var typeResolution: NominalTypeResolution - var nestingDepth = 0 - - init(typeResolution: NominalTypeResolution) { - self.typeResolution = typeResolution - super.init(viewMode: .sourceAccurate) - } - - // Entering nominal type declarations. - - func visitNominal(_ node: NominalTypeDeclSyntaxNode) { - if nestingDepth == 0 { - typeResolution.topLevelNominalTypes[node.name.text] = node - } - - nestingDepth += 1 - } - - override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { - visitNominal(node) - return .visitChildren - } - - override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - visitNominal(node) - return .visitChildren - } - - override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - visitNominal(node) - return .visitChildren - } - - override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - visitNominal(node) - return .visitChildren - } - - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - visitNominal(node) - return .visitChildren - } - - // Exiting nominal type declarations. - func visitPostNominal(_ node: NominalTypeDeclSyntaxNode) { - assert(nestingDepth > 0) - nestingDepth -= 1 - } - - override func visitPost(_ node: ActorDeclSyntax) { - visitPostNominal(node) - } - - override func visitPost(_ node: ClassDeclSyntax) { - visitPostNominal(node) - } - - override func visitPost(_ node: EnumDeclSyntax) { - visitPostNominal(node) - } - - override func visitPost(_ node: ProtocolDeclSyntax) { - visitPostNominal(node) - } - - override func visitPost(_ node: StructDeclSyntax) { - visitPostNominal(node) - } - - // Extension handling - override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - // Note that the extension is unresolved. We'll bind it later. - typeResolution.unresolvedExtensions.append(node) - nestingDepth += 1 - return .visitChildren - } - - override func visitPost(_ node: ExtensionDeclSyntax) { - nestingDepth -= 1 - } - - // Avoid stepping into functions. - - override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren - } - } -} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index c8a26382..19c8fe57 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -87,7 +87,7 @@ extension Swift2JavaTranslator { // === All types for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let fileNameBase = "\(ty.swiftTypeName)+SwiftJava" + let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" let filename = "\(fileNameBase).swift" log.info("Printing contents: \(filename)") @@ -132,7 +132,7 @@ extension Swift2JavaTranslator { } } - public func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws { + package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws { let stt = SwiftThunkTranslator(self) printer.print( @@ -192,7 +192,7 @@ extension Swift2JavaTranslator { } } - public func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + package func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) printImports(&printer) @@ -280,7 +280,7 @@ extension Swift2JavaTranslator { printer.print("") } - public func printNominal( + package func printNominal( _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { let parentProtocol: String @@ -1048,7 +1048,7 @@ extension Swift2JavaTranslator { printer.print(");") } - public func printHeapObjectToStringMethod( + package func printHeapObjectToStringMethod( _ printer: inout CodePrinter, _ decl: ImportedNominalType ) { printer.print( diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index 098333e7..25fd3e44 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -28,7 +28,7 @@ public final class Swift2JavaTranslator { struct Input { let filePath: String - let syntax: Syntax + let syntax: SourceFileSyntax } var inputs: [Input] = [] @@ -51,7 +51,6 @@ public final class Swift2JavaTranslator { package var swiftStdlibTypes: SwiftStandardLibraryTypes let symbolTable: SwiftSymbolTable - let nominalResolution: NominalTypeResolution = NominalTypeResolution() var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry() @@ -90,8 +89,7 @@ extension Swift2JavaTranslator { package func add(filePath: String, text: String) { log.trace("Adding: \(filePath)") let sourceFileSyntax = Parser.parse(source: text) - self.nominalResolution.addSourceFile(sourceFileSyntax) - self.inputs.append(Input(filePath: filePath, syntax: Syntax(sourceFileSyntax))) + self.inputs.append(Input(filePath: filePath, syntax: sourceFileSyntax)) } /// Convenient method for analyzing single file. @@ -120,20 +118,8 @@ extension Swift2JavaTranslator { } package func prepareForTranslation() { - nominalResolution.bindExtensions() - - // Prepare symbol table for nominal type names. - for (_, node) in nominalResolution.topLevelNominalTypes { - symbolTable.parsedModule.addNominalTypeDeclaration(node, parent: nil) - } - - for (ext, nominalNode) in nominalResolution.resolvedExtensions { - guard let nominalDecl = symbolTable.parsedModule.lookup(nominalNode) else { - continue - } - - symbolTable.parsedModule.addExtension(ext, extending: nominalDecl) - } + /// Setup the symbol table. + symbolTable.setup(inputs.map({ $0.syntax })) } } @@ -164,18 +150,42 @@ extension Swift2JavaTranslator { // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Type translation extension Swift2JavaTranslator { - /// Try to resolve the given nominal type node into its imported - /// representation. + /// Try to resolve the given nominal declaration node into its imported representation. func importedNominalType( - _ nominal: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax + _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax, + parent: ImportedNominalType? ) -> ImportedNominalType? { - if !nominal.shouldImport(log: log) { + if !nominalNode.shouldImport(log: log) { return nil } - guard let fullName = nominalResolution.fullyQualifiedName(of: nominal) else { + guard let nominal = symbolTable.lookupType(nominalNode.name.text, parent: parent?.swiftNominal) else { return nil } + return self.importedNominalType(nominal) + } + + /// Try to resolve the given nominal type node into its imported representation. + func importedNominalType( + _ typeNode: TypeSyntax + ) -> ImportedNominalType? { + guard let swiftType = try? SwiftType(typeNode, symbolTable: self.symbolTable) else { + return nil + } + guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else { + return nil + } + guard let nominalNode = symbolTable.parsedModule.nominalTypeSyntaxNodes[swiftNominalDecl] else { + return nil + } + guard nominalNode.shouldImport(log: log) else { + return nil + } + return importedNominalType(swiftNominalDecl) + } + + func importedNominalType(_ nominal: SwiftNominalTypeDeclaration) -> ImportedNominalType? { + let fullName = nominal.qualifiedName if let alreadyImported = importedTypes[fullName] { return alreadyImported @@ -183,19 +193,19 @@ extension Swift2JavaTranslator { // 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 + switch nominal.kind { + case .actor: kind = .actor + case .class: kind = .class + case .enum: kind = .enum + case .struct: kind = .struct default: return nil } let importedNominal = ImportedNominalType( - swiftTypeName: fullName, + swiftNominal: nominal, javaType: .class( package: javaPackage, - name: fullName + name: nominal.qualifiedName ), kind: kind ) diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 96aab517..3df49782 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -33,7 +33,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { var currentType: ImportedNominalType? { typeContext.last?.type } /// The current type name as a nested name like A.B.C. - var currentTypeName: String? { self.currentType?.swiftTypeName } + var currentTypeName: String? { self.currentType?.swiftNominal.qualifiedName } var log: Logger { translator.log } @@ -62,7 +62,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { log.debug("Visit \(node.kind): '\(node.qualifiedNameForDebug)'") - guard let importedNominalType = translator.importedNominalType(node) else { + guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else { return .skipChildren } @@ -78,7 +78,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { log.debug("Visit \(node.kind): \(node.qualifiedNameForDebug)") - guard let importedNominalType = translator.importedNominalType(node) else { + guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else { return .skipChildren } @@ -95,9 +95,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { // 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 = translator.importedNominalType(nominal) - else { + guard let importedNominalType = translator.importedNominalType(node.extendedType) else { return .skipChildren } diff --git a/Sources/JExtractSwift/SwiftKit+Printing.swift b/Sources/JExtractSwift/SwiftKit+Printing.swift index ed7d05d6..aa01502d 100644 --- a/Sources/JExtractSwift/SwiftKit+Printing.swift +++ b/Sources/JExtractSwift/SwiftKit+Printing.swift @@ -23,7 +23,7 @@ package struct SwiftKitPrinting { /// Forms syntax for a Java call to a swiftkit exposed function. static func renderCallGetSwiftType(module: String, nominal: ImportedNominalType) -> String { """ - SwiftKit.swiftjava.getType("\(module)", "\(nominal.swiftTypeName)") + SwiftKit.swiftjava.getType("\(module)", "\(nominal.swiftNominal.qualifiedName)") """ } } @@ -38,7 +38,7 @@ extension SwiftKitPrinting { extension SwiftKitPrinting.Names { static func getType(module: String, nominal: ImportedNominalType) -> String { - "swiftjava_getType_\(module)_\(nominal.swiftTypeName)" + "swiftjava_getType_\(module)_\(nominal.swiftNominal.qualifiedName)" } } diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift index 2676ef2e..6a423a78 100644 --- a/Sources/JExtractSwift/SwiftThunkTranslator.swift +++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift @@ -73,7 +73,7 @@ struct SwiftThunkTranslator { """ @_cdecl("\(raw: funcName)") public func \(raw: funcName)() -> UnsafeMutableRawPointer /* Any.Type */ { - return unsafeBitCast(\(raw: nominal.swiftTypeName).self, to: UnsafeMutableRawPointer.self) + return unsafeBitCast(\(raw: nominal.swiftNominal.qualifiedName).self, to: UnsafeMutableRawPointer.self) } """ } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift index 53e103b0..5e1c0b18 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -14,9 +14,13 @@ import SwiftSyntax +///// A syntax node for a nominal type declaration. +@_spi(Testing) +public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax + /// Describes a nominal type declaration, which can be of any kind (class, struct, etc.) /// and has a name, parent type (if nested), and owning module. -class SwiftNominalTypeDeclaration { +package class SwiftNominalTypeDeclaration { enum Kind { case actor case `class` @@ -79,16 +83,24 @@ class SwiftNominalTypeDeclaration { return KnownStandardLibraryType(typeNameInSwiftModule: name) } + + package var qualifiedName: String { + if let parent = self.parent { + return parent.qualifiedName + "." + name + } else { + return name + } + } } extension SwiftNominalTypeDeclaration: Equatable { - static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool { + package static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool { lhs === rhs } } extension SwiftNominalTypeDeclaration: Hashable { - func hash(into hasher: inout Hasher) { + package func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift index bb3c2f5f..87c2c80f 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift @@ -14,7 +14,7 @@ import SwiftSyntax -protocol SwiftSymbolTableProtocol { +package protocol SwiftSymbolTableProtocol { /// The module name that this symbol table describes. var moduleName: String { get } @@ -28,7 +28,7 @@ protocol SwiftSymbolTableProtocol { extension SwiftSymbolTableProtocol { /// Look for a type - func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? { + package func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? { if let parent { return lookupNestedType(name, parent: parent) } @@ -37,19 +37,62 @@ extension SwiftSymbolTableProtocol { } } -class SwiftSymbolTable { +package class SwiftSymbolTable { var importedModules: [SwiftModuleSymbolTable] = [] var parsedModule: SwiftParsedModuleSymbolTable - init(parsedModuleName: String) { + package init(parsedModuleName: String) { self.parsedModule = SwiftParsedModuleSymbolTable(moduleName: parsedModuleName) } func addImportedModule(symbolTable: SwiftModuleSymbolTable) { importedModules.append(symbolTable) } +} + +extension SwiftSymbolTable { + package func setup(_ sourceFiles: some Collection) { + // First, register top-level and nested nominal types to the symbol table. + 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) + } + } + } + } + + 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 + } + assert(numExtensionsBefore > unresolvedExtensions.count) + } + } - func addTopLevelNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) { + private func addNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) { // Find top-level nominal type declarations. for statement in sourceFile.statements { // We only care about declarations. @@ -62,31 +105,31 @@ class SwiftSymbolTable { } } - func addExtensions( - _ sourceFile: SourceFileSyntax, - nominalResolution: NominalTypeResolution - ) { - // Find extensions. - for statement in sourceFile.statements { - // We only care about declarations. - guard case .decl(let decl) = statement.item, - let extNode = decl.as(ExtensionDeclSyntax.self), - let extendedTypeNode = nominalResolution.extendedType(of: extNode), - let extendedTypeDecl = parsedModule.nominalTypeDeclarations[extendedTypeNode.id] else { - continue - } - - parsedModule.addExtension(extNode, extending: extendedTypeDecl) + 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 } + + // 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 } } extension SwiftSymbolTable: SwiftSymbolTableProtocol { - var moduleName: String { parsedModule.moduleName } + package var moduleName: String { parsedModule.moduleName } /// Look for a top-level nominal type with the given name. This should only /// return nominal types within this module. - func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { + package func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { if let parsedResult = parsedModule.lookupTopLevelNominalType(name) { return parsedResult } @@ -101,7 +144,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { } // Look for a nested type with the given name. - func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { + package func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { if let parsedResult = parsedModule.lookupNestedType(name, parent: parent) { return parsedResult } diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index b36d7e77..19c38ce6 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -158,16 +158,13 @@ extension Swift2JavaVisitor { ) } - // Generic types aren't mapped into Java. + // FIXME: Generic types aren't mapped into Java. if let genericArguments { throw TypeTranslationError.unexpectedGenericArguments(type, genericArguments) } // 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 { + guard let importedNominal = translator.importedNominalType(type) else { throw TypeTranslationError.unknown(type) } diff --git a/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift b/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift deleted file mode 100644 index 01b507df..00000000 --- a/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -@_spi(Testing) import JExtractSwift -import SwiftSyntax -import SwiftParser -import Testing - -@Suite("Nominal type lookup") -struct NominalTypeLookupSuite { - func checkNominalRoundTrip( - _ resolution: NominalTypeResolution, - name: String, - fileID: String = #fileID, - filePath: String = #filePath, - line: Int = #line, - column: Int = #column - ) { - let sourceLocation = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column) - let nominal = resolution.resolveNominalType(name) - #expect(nominal != nil, sourceLocation: sourceLocation) - if let nominal { - #expect(resolution.fullyQualifiedName(of: nominal) == name, sourceLocation: sourceLocation) - } - } - - @Test func lookupBindingTests() { - let resolution = NominalTypeResolution() - resolution.addSourceFile(""" - extension X { - struct Y { - } - } - - struct X { - } - - extension X.Y { - struct Z { } - } - """) - - // Bind all extensions and verify that all were bound. - #expect(resolution.bindExtensions().isEmpty) - - checkNominalRoundTrip(resolution, name: "X") - checkNominalRoundTrip(resolution, name: "X.Y") - checkNominalRoundTrip(resolution, name: "X.Y.Z") - #expect(resolution.resolveNominalType("Y") == nil) - #expect(resolution.resolveNominalType("X.Z") == nil) - } -} - diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift new file mode 100644 index 00000000..5d1e5e2b --- /dev/null +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-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 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import JExtractSwift +import SwiftSyntax +import SwiftParser +import Testing + +@Suite("Swift symbol table") +struct SwiftSymbolTableSuite { + + @Test func lookupBindingTests() throws { + let symbolTable = SwiftSymbolTable(parsedModuleName: "MyModule") + let sourceFile1: SourceFileSyntax = """ + extension X.Y { + struct Z { } + } + extension X { + struct Y {} + } + """ + let sourceFile2: SourceFileSyntax = """ + struct X {} + """ + + symbolTable.setup([sourceFile1, sourceFile2]) + + let x = try #require(symbolTable.lookupType("X", parent: nil)) + let xy = try #require(symbolTable.lookupType("Y", parent: x)) + let xyz = try #require(symbolTable.lookupType("Z", parent: xy)) + #expect(xyz.qualifiedName == "X.Y.Z") + + #expect(symbolTable.lookupType("Y", parent: nil) == nil) + #expect(symbolTable.lookupType("Z", parent: nil) == nil) + } +}