diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 560ed9c0c..0887cea90 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -174,11 +174,37 @@ extension SwiftParsedModuleSymbolTableBuilder { 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(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalMember, parent: parent) - } else if let typeAliasMember = member.decl.as(TypeAliasDeclSyntax.self) { - self.handle(sourceFilePath: sourceFilePath, typeAliasDecl: typeAliasMember, parent: parent) + self.handle(sourceFilePath: sourceFilePath, memberDecl: member.decl, parent: parent) + } + } + + /// Add a nested type declaration to the symbol table. + package mutating func handle( + sourceFilePath: String, + memberDecl decl: DeclSyntax, + parent: SwiftNominalTypeDeclaration + ) { + if let nominalMember = decl.asNominal { + self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalMember, parent: parent) + } else if let typeAliasMember = decl.as(TypeAliasDeclSyntax.self) { + self.handle(sourceFilePath: sourceFilePath, typeAliasDecl: typeAliasMember, parent: parent) + } else if let ifConfigNode = decl.as(IfConfigDeclSyntax.self) { + let (clause, _) = ifConfigNode.activeClause(in: buildConfig) + if let clause, let elements = clause.elements { + switch elements { + case .decls(let memberBlock): + for memberItem in memberBlock { + self.handle(sourceFilePath: sourceFilePath, memberDecl: memberItem.decl, parent: parent) + } + case .statements(let codeBlock): + for codeItem in codeBlock { + if let declNode = codeItem.item.as(DeclSyntax.self) { + self.handle(sourceFilePath: sourceFilePath, memberDecl: declNode, parent: parent) + } + } + default: + break + } } } } diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/SwiftExtract/SwiftTypes/SwiftTypeLookupContext.swift index a07ee6e78..e50ceb950 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftTypeLookupContext.swift @@ -212,6 +212,12 @@ public class SwiftTypeLookupContext { return (try typeDeclaration(for: parentDecl, sourceFilePath: "FIXME_NO_SOURCE_FILE.swift") as! SwiftNominalTypeDeclaration) // FIXME: need to get the source file of the parent + case .extensionDecl(let extensionNode): + if let extendedNominal = extendedNominal(of: extensionNode) { + return extendedNominal + } + node = parentDecl + continue default: node = parentDecl continue diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift index 159e496e5..ea052c56a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -102,6 +102,178 @@ struct JNINestedTypesTests { ) } + @Test("Import: shadowed nested type name") + func shadowedNestedTypeName_java() throws { + // A nested type whose name shadows a type of the same name in an outer + // scope must resolve to the innermost declaration. + try assertOutput( + input: """ + public struct Outer { + public enum Kind { + case alpha + case beta + } + + public struct Inner { + public enum Kind { + case one + case two + } + + public var kind: Kind // Outer.Inner.Kind + } + + public var kind: Kind // Outer.Kind + public var inner: Inner + } + """, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + // The `Inner.kind` property must reference `Outer.Inner.Kind`, not `Outer.Kind`. + """ + public Outer.Inner.Kind getKind(SwiftArena swiftArena) { + return Outer.Inner.Kind.wrapMemoryAddressUnsafe(Outer.Inner.$getKind(this.$memoryAddress()), swiftArena); + """, + """ + public void setKind(Outer.Inner.Kind newValue) { + Outer.Inner.$setKind(newValue.$memoryAddress(), this.$memoryAddress()); + """, + // The outer `kind` must reference `Outer.Kind`. + """ + public Outer.Kind getKind(SwiftArena swiftArena) { + return Outer.Kind.wrapMemoryAddressUnsafe(Outer.$getKind(this.$memoryAddress()), swiftArena); + """, + ] + ) + } + + @Test("Import: shadowed nested type name inside #if") + func shadowedNestedTypeName_ifConfig_java() throws { + try assertOutput( + input: """ + public struct Outer { + #if SOME_FLAG + public enum Kind { + case alphaX + } + #else + public enum Kind { + case alpha + case beta + } + #endif + + public struct Inner { + #if SOME_FLAG + public enum Kind { + case oneX + } + #else + public enum Kind { + case one + case two + } + #endif + + public var kind: Kind // Outer.Inner.Kind + } + + public var kind: Kind // Outer.Kind + public var inner: Inner + } + """, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public Outer.Inner.Kind getKind(SwiftArena swiftArena) { + return Outer.Inner.Kind.wrapMemoryAddressUnsafe(Outer.Inner.$getKind(this.$memoryAddress()), swiftArena); + """, + """ + public void setKind(Outer.Inner.Kind newValue) { + Outer.Inner.$setKind(newValue.$memoryAddress(), this.$memoryAddress()); + """, + ] + ) + } + + @Test("Import: shadowed nested type name in deeper nesting") + func shadowedNestedTypeName_deep_java() throws { + try assertOutput( + input: """ + public struct DiscordChannel { + public struct Kind { + public init() {} + } + + public struct Message { + public struct MessageReference { + public struct Kind { + public init() {} + } + public var kind: Kind + } + } + } + """, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + // The `MessageReference.kind` property must reference the deeply nested + // `DiscordChannel.Message.MessageReference.Kind`, not `DiscordChannel.Kind`. + """ + public DiscordChannel.Message.MessageReference.Kind getKind(SwiftArena swiftArena) { + """, + """ + public void setKind(DiscordChannel.Message.MessageReference.Kind newValue) { + """, + ] + ) + } + + @Test("Import: shadowed nested type name declared via extension") + func shadowedNestedTypeName_extension_java() throws { + try assertOutput( + input: """ + public struct Outer { + public enum Kind { + case alpha + case beta + } + } + + extension Outer { + public struct Inner { + public enum Kind { + case one + case two + } + + public var kind: Kind // Outer.Inner.Kind + } + } + """, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + // The `Inner.kind` property must reference `Outer.Inner.Kind`, not `Outer.Kind`. + """ + public Outer.Inner.Kind getKind(SwiftArena swiftArena) { + return Outer.Inner.Kind.wrapMemoryAddressUnsafe(Outer.Inner.$getKind(this.$memoryAddress()), swiftArena); + """, + """ + public void setKind(Outer.Inner.Kind newValue) { + Outer.Inner.$setKind(newValue.$memoryAddress(), this.$memoryAddress()); + """, + ] + ) + } + @Test("Import: nested in enum") func nestedEnums_java() throws { try assertOutput(