[clang-tools-extra] [clangd][modules] Support go-to-definition on module imports (PR #219839)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 30 12:34:50 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangd
Author: Berkay Sahin (berkaysahiin)
<details>
<summary>Changes</summary>
---
Full diff: https://github.com/llvm/llvm-project/pull/219839.diff
2 Files Affected:
- (modified) clang-tools-extra/clangd/XRefs.cpp (+54)
- (modified) clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp (+56)
``````````diff
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 86528d806eab3..f51039f33992b 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -42,6 +42,7 @@
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Basic/LLVM.h"
+#include "clang/Basic/Module.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TokenKinds.h"
@@ -235,6 +236,54 @@ std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc,
return L;
}
+std::optional<LocatedSymbol>
+locateModuleReferent(const syntax::Token &TouchedIdentifier, ParsedAST &AST,
+ llvm::StringRef MainFilePath) {
+ const SourceManager &SM = AST.getSourceManager();
+ const ASTContext &Context = AST.getASTContext();
+
+ const Module *ResultModule = nullptr;
+
+ for (const ImportDecl *Import : Context.local_imports()) {
+ const Module *Imported = Import->getImportedModule();
+ ArrayRef<SourceLocation> IdentifierLocs = Import->getIdentifierLocs();
+ if (!Imported || !Imported->isNamedModule() || IdentifierLocs.empty())
+ continue;
+
+ std::string Name = Imported->getFullModuleName();
+ if (auto Colon = Name.find(':'); Colon != std::string::npos)
+ Name.erase(0, Colon + 1);
+ if (Name.empty())
+ continue;
+
+ const size_t NameSize = static_cast<int>(Name.size() - 1);
+
+ const SourceLocation NameBegin = SM.getSpellingLoc(IdentifierLocs.front());
+ const SourceLocation NameEnd = NameBegin.getLocWithOffset(NameSize);
+
+ if (SM.isPointWithin(TouchedIdentifier.location(), NameBegin, NameEnd)) {
+ ResultModule = Imported;
+ break;
+ }
+ }
+
+ if (!ResultModule)
+ return std::nullopt;
+
+ const SourceLocation DefinitionLoc =
+ SM.getSpellingLoc(ResultModule->DefinitionLoc);
+ auto Definition = makeLocation(Context, DefinitionLoc, MainFilePath);
+
+ if (!Definition)
+ return std::nullopt;
+
+ LocatedSymbol Result;
+ Result.Name = ResultModule->getFullModuleName();
+ Result.PreferredDeclaration = *Definition;
+ Result.Definition = *Definition;
+ return Result;
+}
+
// Treat #included files as symbols, to enable go-to-definition on them.
std::optional<LocatedSymbol> locateFileReferent(const Position &Pos,
ParsedAST &AST,
@@ -865,6 +914,11 @@ std::vector<LocatedSymbol> locateSymbolAt(ParsedAST &AST, Position Pos,
}
}
+ if (TouchedIdentifier)
+ if (auto Module =
+ locateModuleReferent(*TouchedIdentifier, AST, MainFilePath))
+ return {*std::move(Module)};
+
ASTNodeKind NodeKind;
auto ASTResults = locateASTReferent(*CurLoc, TouchedIdentifier, AST,
MainFilePath, Index, NodeKind);
diff --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
index f10f3e4976cf9..8933a21108e0e 100644
--- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
+++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
@@ -19,6 +19,7 @@
#include "ProjectModules.h"
#include "SemanticHighlighting.h"
#include "TestTU.h"
+#include "XRefs.h"
#include "support/Path.h"
#include "support/ThreadsafeFS.h"
#include "clang/Tooling/Tooling.h"
@@ -629,6 +630,61 @@ import A;
EXPECT_TRUE(D.isFromASTFile());
}
+TEST_F(PrerequisiteModulesTests, LocateImportedModule) {
+ MockDirectoryCompilationDatabase CDB(TestDir, FS);
+
+ Annotations Dep(R"cpp(
+export $decl[[module]] dep.one.two;
+)cpp");
+ CDB.addFile("Dep.cppm", Dep.code());
+
+ Annotations Part(R"cpp(
+export $decl[[module]] M:part.one;
+)cpp");
+ CDB.addFile("M-part.cppm", Part.code());
+
+ Annotations Use(R"cpp(
+export module M;
+import $dep0^dep.$dep1^one.$dep2^two;
+import :$part0^part.$part1^one;
+)cpp");
+ CDB.addFile("M.cppm", Use.code());
+
+ ModulesBuilder Builder(CDB);
+ auto Inputs = getInputs("M.cppm", CDB);
+ Inputs.ModulesManager = &Builder;
+ Inputs.Opts.SkipPreambleBuild = true;
+
+ auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
+ ASSERT_TRUE(CI);
+ auto Preamble =
+ buildPreamble(getFullPath("M.cppm"), *CI, Inputs, /*InMemory=*/true,
+ /*Callback=*/nullptr);
+ ASSERT_TRUE(Preamble);
+
+ auto AST = ParsedAST::build(getFullPath("M.cppm"), Inputs, std::move(CI), {},
+ Preamble);
+ ASSERT_TRUE(AST);
+ ASSERT_TRUE(AST->getDiagnostics().empty());
+
+ auto Check = [&](llvm::StringRef Point, llvm::StringRef Name,
+ llvm::StringRef File, Range TargetRange) {
+ auto Results = locateSymbolAt(*AST, Use.point(Point));
+ ASSERT_THAT(Results, testing::SizeIs(1));
+ EXPECT_EQ(Results.front().Name, Name);
+ Location Target{
+ URIForFile::canonicalize(getFullPath(File), getFullPath("M.cppm")),
+ TargetRange};
+ EXPECT_EQ(Results.front().PreferredDeclaration, Target);
+ EXPECT_EQ(Results.front().Definition, Target);
+ };
+
+ for (llvm::StringRef Point : {"dep0", "dep1", "dep2"})
+ Check(Point, "dep.one.two", "Dep.cppm", Dep.range("decl"));
+ for (llvm::StringRef Point : {"part0", "part1"})
+ Check(Point, "M:part.one", "M-part.cppm", Part.range("decl"));
+}
+
// An end to end test for code complete in modules
TEST_F(PrerequisiteModulesTests, CodeCompleteTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
``````````
</details>
https://github.com/llvm/llvm-project/pull/219839
More information about the cfe-commits
mailing list