diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 106247a4..7455bab6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -103,7 +103,7 @@ extension Swift2JavaTranslator { } package func add(filePath: String, text: String) { - log.info("Adding: \(filePath)") + log.debug("Adding: \(filePath)") let sourceFileSyntax = Parser.parse(source: text) self.inputs.append(SwiftJavaInputFile(syntax: sourceFileSyntax, path: filePath)) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 5ba824c4..34e8c6a4 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -153,11 +153,17 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { } } - // FIXME: Implement module qualified name lookups. E.g. 'Swift.String' - return nil } + /// Look for a top-level nominal type in a specific module by name + package func lookupTopLevelNominalType(_ name: String, inModule moduleName: String) -> SwiftNominalTypeDeclaration? { + if moduleName == self.moduleName { + return parsedModule.lookupTopLevelNominalType(name) + } + return importedModules[moduleName]?.lookupTopLevelNominalType(name) + } + // Look for a nested type with the given name. package func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { if let parsedResult = parsedModule.lookupNestedType(name, parent: parent) { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 0de2ca19..e0752ea6 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -353,16 +353,18 @@ extension SwiftType { self = knownTypes.optionalSugar(try SwiftType(optionalType.wrappedType, lookupContext: lookupContext)) case .memberType(let memberType): - // If the parent type isn't a known module, translate it. - // FIXME: Need a more reasonable notion of which names are module names - // for this to work. What can we query for this information? + // If the parent type is a known module name, perform a module-qualified + // lookup instead of treating the module as a parent type let parentType: SwiftType? + let moduleName: String? if let base = memberType.baseType.as(IdentifierTypeSyntax.self), lookupContext.symbolTable.isModuleName(base.name.trimmedDescription) { parentType = nil + moduleName = base.name.trimmedDescription } else { parentType = try SwiftType(memberType.baseType, lookupContext: lookupContext) + moduleName = nil } // Translate the generic arguments. @@ -382,7 +384,8 @@ extension SwiftType { parent: parentType, name: memberType.name, genericArguments: genericArgs, - lookupContext: lookupContext + lookupContext: lookupContext, + module: moduleName ) case .metatypeType(let metatypeType): @@ -431,7 +434,8 @@ extension SwiftType { parent: SwiftType?, name: TokenSyntax, genericArguments: [SwiftType]?, - lookupContext: SwiftTypeLookupContext + lookupContext: SwiftTypeLookupContext, + module: String? = nil ) throws { // Look up the imported types by name to resolve it to a nominal type. let typeDecl: SwiftTypeDeclaration? @@ -440,6 +444,8 @@ extension SwiftType { throw TypeTranslationError.unknown(originalType) } typeDecl = lookupContext.symbolTable.lookupNestedType(name.text, parent: parentDecl) + } else if let module { + typeDecl = lookupContext.moduleQualifiedLookup(name: name.text, in: module) } else { guard let ident = Identifier(name) else { throw TypeTranslationError.unknown(originalType) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 28b0e4d0..71aec972 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -15,8 +15,8 @@ @_spi(Experimental) import SwiftLexicalLookup import SwiftSyntax -/// Unqualified type lookup manager. -/// All unqualified lookup should be done via this instance. This caches the +/// Type lookup manager. +/// All type lookups should be done via this instance. This caches the /// association of `Syntax.ID` to `SwiftTypeDeclaration`, and guarantees that /// there's only one `SwiftTypeDeclaration` per declaration `Syntax`. class SwiftTypeLookupContext { @@ -28,6 +28,15 @@ class SwiftTypeLookupContext { self.symbolTable = symbolTable } + /// Perform module-qualified type lookup in a specific module + /// + /// - Parameters: + /// - name: name to lookup + /// - moduleName: the module to look in + func moduleQualifiedLookup(name: String, in moduleName: String) -> SwiftTypeDeclaration? { + symbolTable.lookupTopLevelNominalType(name, inModule: moduleName) + } + /// Perform unqualified type lookup. /// /// - Parameters: diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index bdf00de1..66a725f1 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -81,7 +81,7 @@ struct SwiftSymbolTableSuite { public struct MyValue {} } - public func fullyQualifiedType() -> MyModule.MyModule.MyValue + public func fullyQualifiedType() -> MyModule.MyModule.MyValue """, mode, .java, @@ -92,4 +92,94 @@ struct SwiftSymbolTableSuite { ], ) } + + @Test func moduleScopedLookup() throws { + let sourceFile: SourceFileSyntax = """ + public struct MyClass {} + """ + let symbolTable = SwiftSymbolTable.setup( + moduleName: "MyModule", + [ + .init(syntax: sourceFile, path: "Fake.swift") + ], + config: nil, + log: Logger(label: "swift-java", logLevel: .critical), + ) + + // Lookup in self-module by qualified name + let myClass = symbolTable.lookupTopLevelNominalType("MyClass", inModule: "MyModule") + #expect(myClass != nil) + #expect(myClass?.qualifiedName == "MyClass") + + // Lookup in imported module (Swift) + let swiftInt = symbolTable.lookupTopLevelNominalType("Int", inModule: "Swift") + #expect(swiftInt != nil) + #expect(swiftInt?.qualifiedName == "Int") + + // Lookup in unknown module returns nil + let unknown = symbolTable.lookupTopLevelNominalType("Foo", inModule: "NoSuchModule") + #expect(unknown == nil) + } + + @Test(arguments: [JExtractGenerationMode.jni, .ffm]) + func resolveQualifiedTypesInFunctionSignatures(mode: JExtractGenerationMode) throws { + try assertOutput( + input: """ + public struct MySwiftClass { + public init() {} + } + + public func factory(len: Swift.Int, cap: Swift.Int) -> MyModule.MySwiftClass + """, + mode, + .java, + swiftModuleName: "MyModule", + detectChunkByInitialLines: 1, + expectedChunks: [ + "public static MySwiftClass factory(" + ], + ) + } + + @Test(arguments: [JExtractGenerationMode.jni, .ffm]) + func resolveQualifiedNestedTypesInFunctionSignatures(mode: JExtractGenerationMode) throws { + try assertOutput( + input: """ + public struct MySwiftClass { + public struct Nested { + public init() {} + } + } + + public func factory(len: Swift.Int, cap: Swift.Int) -> MyModule.MySwiftClass.Nested + """, + mode, + .java, + swiftModuleName: "MyModule", + detectChunkByInitialLines: 1, + expectedChunks: [ + "public static MySwiftClass.Nested factory(" + ], + ) + } + + @Test(arguments: [JExtractGenerationMode.jni, .ffm]) + func resolveQualifiedTypesShadowingModule(mode: JExtractGenerationMode) throws { + try assertOutput( + input: """ + public struct MyModule { // shadowing module MyModule + public init() {} + } + + public func factory(len: Swift.Int, cap: Swift.Int) -> MyModule + """, + mode, + .java, + swiftModuleName: "MyModule", + detectChunkByInitialLines: 1, + expectedChunks: [ + "public static MyModule factory(" + ], + ) + } }