From d40ea83424194f648a681f4db8268e9599c38545 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 28 Aug 2025 09:03:42 +0200 Subject: [PATCH] Fix issue that causes type hierarchy to not work for clang files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clang’s `textDocument/symbolInfo` request does not return a `kind` field. We were thus filtering all symbols from clang, which left us with no USR that could be used for the type hierarchy. --- Sources/SourceKitLSP/SourceKitLSPServer.swift | 31 +++++++++----- .../TypeHierarchyTests.swift | 41 +++++++++++++++++++ 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 58b5eb835..1ddd7099b 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -2464,21 +2464,32 @@ extension SourceKitLSPServer { guard let index = await workspaceForDocument(uri: req.textDocument.uri)?.index(checkedFor: .deletedFiles) else { return nil } - let usrs = - symbols - .filter { - // Only include references to type. For example, we don't want to find the type hierarchy of a constructor when - // starting the type hierarchy on `Foo()``. - switch $0.kind { - case .class, .enum, .interface, .struct: return true - default: return false - } + let usrs = symbols.filter { + // Only include references to type. For example, we don't want to find the type hierarchy of a constructor when + // starting the type hierarchy on `Foo()`. + // Consider a symbol a class if its kind is `nil`, eg. for a symbol returned by clang's SymbolInfo, which + // doesn't support the `kind` field. + switch $0.kind { + case .class, .enum, .interface, .struct, nil: return true + default: return false } - .compactMap(\.usr) + }.compactMap(\.usr) + let typeHierarchyItems = usrs.compactMap { (usr) -> TypeHierarchyItem? in guard let info = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else { return nil } + // Filter symbols based on their kind in the index since the filter on the symbol info response might have + // returned `nil` for the kind, preventing us from doing any filtering there. + switch info.symbol.kind { + case .unknown, .macro, .function, .variable, .field, .enumConstant, .instanceMethod, .classMethod, .staticMethod, + .instanceProperty, .classProperty, .staticProperty, .constructor, .destructor, .conversionFunction, .parameter, + .concept, .commentTag: + return nil + case .module, .namespace, .namespaceAlias, .enum, .struct, .class, .protocol, .extension, .union, .typealias, + .using: + break + } return self.indexToLSPTypeHierarchyItem( definition: info, moduleName: info.location.moduleName, diff --git a/Tests/SourceKitLSPTests/TypeHierarchyTests.swift b/Tests/SourceKitLSPTests/TypeHierarchyTests.swift index 1fea83261..8ceccd061 100644 --- a/Tests/SourceKitLSPTests/TypeHierarchyTests.swift +++ b/Tests/SourceKitLSPTests/TypeHierarchyTests.swift @@ -12,6 +12,7 @@ import LanguageServerProtocol import SKTestSupport +import SwiftExtensions import TSCBasic import XCTest @@ -248,6 +249,46 @@ final class TypeHierarchyTests: XCTestCase { ] ) } + + func testClangTypeHierarchy() async throws { + let project = try await SwiftPMTestProject( + files: [ + "MyLibrary/include/empty.h": "", + "MyLibrary/Test.cpp": """ + class Foo {}; + + class Bar: 1️⃣Foo {}; + """, + ], + enableBackgroundIndexing: true + ) + let (uri, positions) = try project.openDocument("Test.cpp", language: .cpp) + let response = try await project.testClient.send( + TypeHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"]) + ) + XCTAssertEqual(response?.count, 1) + } + + func testClangTypeHierarchyInitiatedFromFunction() async throws { + let project = try await SwiftPMTestProject( + files: [ + "MyLibrary/include/empty.h": "", + "MyLibrary/Test.cpp": """ + void hello() {} + + void test() { + 1️⃣hello(); + } + """, + ], + enableBackgroundIndexing: true + ) + let (uri, positions) = try project.openDocument("Test.cpp", language: .cpp) + let response = try await project.testClient.send( + TypeHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"]) + ) + XCTAssertNil(response) + } } // MARK: - Utilities