diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index 725061f5..d6e0119e 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -25,19 +25,28 @@ public struct ImportedProtocol: ImportedDecl { public var identifier: String } -public struct ImportedClass: ImportedDecl { +/// Describes a Swift nominal type (e.g., a class, struct, enum) that has been +/// imported and is being translated into Java. +public struct ImportedNominalType: ImportedDecl { public var name: ImportedTypeName - - public var implementedInterfaces: Set = [] + public var kind: NominalTypeKind public var initializers: [ImportedFunc] = [] public var methods: [ImportedFunc] = [] - public init(name: ImportedTypeName) { + public init(name: ImportedTypeName, kind: NominalTypeKind) { self.name = name + self.kind = kind } } +public enum NominalTypeKind { + case `actor` + case `class` + case `enum` + case `struct` +} + public struct ImportedParam: Hashable { let param: FunctionParameterSyntax @@ -99,9 +108,10 @@ public struct ImportedTypeName: Hashable { javaType.className } - public init(swiftTypeName: String, javaType: JavaType) { + public init(swiftTypeName: String, javaType: JavaType, swiftMangledName: String? = nil) { self.swiftTypeName = swiftTypeName self.javaType = javaType + self.swiftMangledName = swiftMangledName ?? "" } } @@ -126,6 +136,8 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { /// This is a full name such as init(cap:name:). public var identifier: String + /// This is the base identifier for the function, e.g., "init" for an + /// initializer or "f" for "f(a:b:)". public var baseIdentifier: String { guard let idx = identifier.firstIndex(of: "(") else { return identifier @@ -134,7 +146,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { } /// A display name to use to refer to the Swift declaration with its - /// enclosing type. + /// enclosing type, if there is one. public var displayName: String { if let parentName { return "\(parentName.swiftTypeName).\(identifier)" diff --git a/Sources/JExtractSwift/NominalTypeResolution.swift b/Sources/JExtractSwift/NominalTypeResolution.swift new file mode 100644 index 00000000..c655598c --- /dev/null +++ b/Sources/JExtractSwift/NominalTypeResolution.swift @@ -0,0 +1,353 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +// 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. + private 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. + private var topLevelNominalTypes: [String: NominalTypeDeclSyntaxNode] = [:] + + @_spi(Testing) public init() { } +} + +/// A syntax node for a nominal type declaration. +@_spi(Testing) +public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax + +// 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 ba55d12f..158b44ef 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -27,7 +27,7 @@ extension Swift2JavaTranslator { public func writeImportedTypesTo(outputDirectory: String) throws { var printer = CodePrinter() - for ty in importedTypes { + for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let filename = "\(ty.name.javaClassName!).java" log.info("Printing contents: \(filename)") printImportedClass(&printer, ty) @@ -106,7 +106,7 @@ extension Swift2JavaTranslator { } } - public func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedClass) { + public func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) printImports(&printer) // TODO print any imports the file may need, it we talk to other Swift modules @@ -164,7 +164,7 @@ extension Swift2JavaTranslator { printer.print("") } - public func printClass(_ printer: inout CodePrinter, _ decl: ImportedClass, body: (inout CodePrinter) -> Void) { + public func printClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void) { printer.printTypeDecl("public final class \(decl.name.javaClassName!)") { printer in // ==== Storage of the class // FIXME: implement the self storage for the memory address and accessors @@ -270,7 +270,7 @@ extension Swift2JavaTranslator { ) } - private func printClassMemorySegmentConstructor(_ printer: inout CodePrinter, _ decl: ImportedClass) { + private func printClassMemorySegmentConstructor(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printer.print( """ /** Instances are created using static {@code init} methods rather than through the constructor directly. */ @@ -282,7 +282,7 @@ extension Swift2JavaTranslator { } /// Print a property where we can store the "self" pointer of a class. - private func printClassSelfProperty(_ printer: inout CodePrinter, _ decl: ImportedClass) { + private func printClassSelfProperty(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printer.print( """ // Pointer to the referred to class instance's "self". @@ -295,7 +295,7 @@ extension Swift2JavaTranslator { ) } - private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedClass) { + private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printer.print( """ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index e642d9ee..d5396872 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -37,7 +37,11 @@ public final class Swift2JavaTranslator { // TODO: consider how/if we need to store those etc public var importedGlobalFuncs: [ImportedFunc] = [] - public var importedTypes: [ImportedClass] = [] + /// A mapping from Swift type names (e.g., A.B) over to the imported nominal + /// type representation. + public var importedTypes: [String: ImportedNominalType] = [:] + + let nominalResolution: NominalTypeResolution = NominalTypeResolution() public init( javaPackage: String, @@ -80,16 +84,17 @@ extension Swift2JavaTranslator { let sourceFileSyntax = Parser.parse(source: text) + // Find all of the types and extensions, then bind the extensions. + nominalResolution.addSourceFile(sourceFileSyntax) + nominalResolution.bindExtensions() + let visitor = Swift2JavaVisitor( moduleName: self.swiftModuleName, targetJavaPackage: self.javaPackage, - log: log + translator: self ) visitor.walk(sourceFileSyntax) - self.importedGlobalFuncs.append(contentsOf: visitor.javaMethodDecls) - self.importedTypes.append(contentsOf: visitor.javaTypeDecls) - try await self.postProcessImportedDecls() } @@ -119,7 +124,7 @@ extension Swift2JavaTranslator { return funcDecl } - importedTypes = try await importedTypes._mapAsync { tyDecl in + importedTypes = Dictionary(uniqueKeysWithValues: try await importedTypes._mapAsync { (tyName, tyDecl) in var tyDecl = tyDecl log.info("Mapping type: \(tyDecl.name)") @@ -140,8 +145,8 @@ extension Swift2JavaTranslator { return funcDecl } - return tyDecl - } + return (tyName, tyDecl) + }) } } diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 65337b8a..db4625bb 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -16,57 +16,93 @@ import SwiftParser import SwiftSyntax final class Swift2JavaVisitor: SyntaxVisitor { - let log: Logger + let translator: Swift2JavaTranslator /// The Swift module we're visiting declarations in let moduleName: String + /// The target java package we are going to generate types into eventually, /// store this along with type names as we import them. let targetJavaPackage: String - var javaMethodDecls: [ImportedFunc] = [] - var javaTypeDecls: [ImportedClass] = [] + /// The current type name as a nested name like A.B.C. + var currentTypeName: String? = nil - var currentTypeDecl: ImportedClass? = nil + var log: Logger { translator.log } - init(moduleName: String, targetJavaPackage: String, log: Logger) { + init(moduleName: String, targetJavaPackage: String, translator: Swift2JavaTranslator) { self.moduleName = moduleName self.targetJavaPackage = targetJavaPackage - self.log = log + self.translator = translator super.init(viewMode: .all) } - override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - guard node.shouldImport(log: log) else { - return .skipChildren + /// Try to resolve the given nominal type node into its imported + /// representation. + func resolveNominalType( + _ nominal: some DeclGroupSyntax & NamedDeclSyntax, + kind: NominalTypeKind + ) -> ImportedNominalType? { + if !nominal.shouldImport(log: log) { + return nil + } + + guard let fullName = translator.nominalResolution.fullyQualifiedName(of: nominal) else { + return nil } - log.info("Import: \(node.kind) \(node.name)") - currentTypeDecl = ImportedClass( - // TODO: support nested classes (parent name here) + if let alreadyImported = translator.importedTypes[fullName] { + return alreadyImported + } + + let importedNominal = ImportedNominalType( name: ImportedTypeName( - swiftTypeName: node.name.text, + swiftTypeName: fullName, javaType: .class( package: targetJavaPackage, - name: node.name.text - ) - ) + name: fullName + ), + swiftMangledName: nominal.mangledNameFromComment + ), + kind: kind ) - // Retrieve the mangled name, if available. - if let mangledName = node.mangledNameFromComment { - currentTypeDecl!.name.swiftMangledName = mangledName + translator.importedTypes[fullName] = importedNominal + return importedNominal + } + + override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + guard let importedNominalType = resolveNominalType(node, kind: .class) else { + return .skipChildren } + currentTypeName = importedNominalType.name.swiftTypeName return .visitChildren } override func visitPost(_ node: ClassDeclSyntax) { - if let currentTypeDecl { + if currentTypeName != nil { log.info("Completed import: \(node.kind) \(node.name)") - self.javaTypeDecls.append(currentTypeDecl) - self.currentTypeDecl = nil + currentTypeName = nil + } + } + + 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 = resolveNominalType(nominal, kind: .class) else { + return .skipChildren + } + + currentTypeName = importedNominalType.name.swiftTypeName + return .visitChildren + } + + override func visitPost(_ node: ExtensionDeclSyntax) { + if currentTypeName != nil { + currentTypeName = nil } } @@ -113,7 +149,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { let fullName = "\(node.name.text)(\(argumentLabelsStr))" var funcDecl = ImportedFunc( - parentName: currentTypeDecl?.name, + parentName: currentTypeName.map { translator.importedTypes[$0] }??.name, identifier: fullName, returnType: javaResultType, parameters: params @@ -125,26 +161,26 @@ final class Swift2JavaVisitor: SyntaxVisitor { funcDecl.swiftMangledName = mangledName } - if var currentTypeDecl = self.currentTypeDecl { - log.info("Record method in \(currentTypeDecl.name.javaType.description)") - currentTypeDecl.methods.append(funcDecl) - self.currentTypeDecl = currentTypeDecl + if let currentTypeName { + log.info("Record method in \(currentTypeName)") + translator.importedTypes[currentTypeName]?.methods.append(funcDecl) } else { - javaMethodDecls.append(funcDecl) + translator.importedGlobalFuncs.append(funcDecl) } return .skipChildren } override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - guard var currentTypeDecl = self.currentTypeDecl else { + guard let currentTypeName, + let currentType = translator.importedTypes[currentTypeName] else { fatalError("Initializer must be within a current type, was: \(node)") } guard node.shouldImport(log: log) else { return .skipChildren } - self.log.info("Import initializer: \(node.kind) \(currentTypeDecl.name.javaType.description)") + self.log.info("Import initializer: \(node.kind) \(currentType.name.javaType.description)") let params: [ImportedParam] do { params = try node.signature.parameterClause.parameters.map { param in @@ -164,9 +200,9 @@ final class Swift2JavaVisitor: SyntaxVisitor { "init(\(params.compactMap { $0.effectiveName ?? "_" }.joined(separator: ":")))" var funcDecl = ImportedFunc( - parentName: currentTypeDecl.name, + parentName: currentType.name, identifier: initIdentifier, - returnType: currentTypeDecl.name, + returnType: currentType.name, parameters: params ) funcDecl.isInit = true @@ -177,15 +213,14 @@ final class Swift2JavaVisitor: SyntaxVisitor { funcDecl.swiftMangledName = mangledName } - log.info("Record initializer method in \(currentTypeDecl.name.javaType.description): \(funcDecl.identifier)") - currentTypeDecl.initializers.append(funcDecl) - self.currentTypeDecl = currentTypeDecl + log.info("Record initializer method in \(currentType.name.javaType.description): \(funcDecl.identifier)") + translator.importedTypes[currentTypeName]!.initializers.append(funcDecl) return .skipChildren } } -extension ClassDeclSyntax { +extension DeclGroupSyntax where Self: NamedDeclSyntax { func shouldImport(log: Logger) -> Bool { guard (accessControlModifiers.first { $0.isPublic }) != nil else { log.trace("Cannot import \(self.name) because: is not public") diff --git a/Sources/JExtractSwift/SwiftDylib.swift b/Sources/JExtractSwift/SwiftDylib.swift index 8be8f6a7..1f656ea7 100644 --- a/Sources/JExtractSwift/SwiftDylib.swift +++ b/Sources/JExtractSwift/SwiftDylib.swift @@ -31,7 +31,7 @@ package struct SwiftDylib { // FIXME: remove this entire utility; replace with self.log = Logger(label: "SwiftDylib(\(path))", logLevel: .trace) // TODO: take from env } - package func fillInTypeMangledName(_ decl: ImportedClass) async throws -> ImportedClass { + package func fillInTypeMangledName(_ decl: ImportedNominalType) async throws -> ImportedNominalType { // TODO: this is hacky, not precise at all and will be removed entirely guard decl.name.swiftMangledName.isEmpty else { // it was already processed diff --git a/Tests/JExtractSwiftTests/FuncImportTests.swift b/Tests/JExtractSwiftTests/FuncImportTests.swift index 89abf871..04158f8f 100644 --- a/Tests/JExtractSwiftTests/FuncImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncImportTests.swift @@ -36,6 +36,11 @@ final class MethodImportTests { // MANGLED NAME: $s14MySwiftLibrary23globalTakeLongIntString1l3i321sys5Int64V_s5Int32VSStF public func globalTakeIntLongString(i32: Int32, l: Int64, s: String) + extension MySwiftClass { + // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC22helloMemberFunctionInExtension + public func helloMemberInExtension() + } + // MANGLED NAME: $s14MySwiftLibrary0aB5ClassCMa public class MySwiftClass { // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC3len3capACSi_SitcfC @@ -180,9 +185,7 @@ final class MethodImportTests { try await st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) - let funcDecl: ImportedFunc = st.importedTypes.first { - $0.name.javaClassName == "MySwiftClass" - }!.methods.first { + let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "helloMemberFunction" }! @@ -223,9 +226,48 @@ final class MethodImportTests { try await st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) - let funcDecl: ImportedFunc = st.importedTypes.first { - $0.name.javaClassName == "MySwiftClass" - }!.methods.first { + let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { + $0.baseIdentifier == "helloMemberInExtension" + }! + + let output = CodePrinter.toString { printer in + st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .memorySegment) + } + + assertOutput( + output, + expected: + """ + /** + * {@snippet lang=swift : + * public func helloMemberInExtension() + * } + */ + public static void helloMemberInExtension(java.lang.foreign.MemorySegment self$) { + var mh$ = helloMemberInExtension.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(self$); + } + mh$.invokeExact(self$); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ) + } + + func test_method_class_helloMemberFunction_self_wrapper() async throws { + let st = Swift2JavaTranslator( + javaPackage: "com.example.swift", + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .trace + + try await st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + + let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "helloMemberFunction" }! @@ -266,9 +308,7 @@ final class MethodImportTests { try await st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) - let funcDecl: ImportedFunc = st.importedTypes.first { - $0.name.javaClassName == "MySwiftClass" - }!.methods.first { + let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "helloMemberFunction" }! @@ -301,9 +341,7 @@ final class MethodImportTests { try await st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) - let funcDecl: ImportedFunc = st.importedTypes.first { - $0.name.javaClassName == "MySwiftClass" - }!.methods.first { + let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "makeInt" }! diff --git a/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift b/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift new file mode 100644 index 00000000..88b181b1 --- /dev/null +++ b/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +// 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, + fileParh: String = #filePath, + line: Int = #line, + column: Int = #column + ) { + let sourceLocation = SourceLocation(fileID: fileID, filePath: fileParh, 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) + } +} +