Author: Berkay Sahin
Date: 2026-09-04T18:24:10+03:00
New Revision: 4154b56ccc70220dca3fa1226fc8a24bafd90ac3

URL: 
https://github.com/llvm/llvm-project/commit/4154b56ccc70220dca3fa1226fc8a24bafd90ac3
DIFF: 
https://github.com/llvm/llvm-project/commit/4154b56ccc70220dca3fa1226fc8a24bafd90ac3.diff

LOG: [clangd][modules] Support go-to-definition on module imports (#219839)

Fixes https://github.com/clangd/clangd/issues/2310

Added: 
    

Modified: 
    clang-tools-extra/clangd/XRefs.cpp
    clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/XRefs.cpp 
b/clang-tools-extra/clangd/XRefs.cpp
index 86528d806eab3..73d8eb5d00569 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,52 @@ 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;
+
+    const SourceLocation NameBegin = SM.getSpellingLoc(IdentifierLocs.front());
+    // Imports are visited in source order; bail out once we pass the cursor.
+    if (SM.isBeforeInTranslationUnit(TouchedIdentifier.location(), NameBegin))
+      break;
+
+    const std::string FullName = Imported->getFullModuleName();
+    const SourceLocation NameEnd =
+        NameBegin.getLocWithOffset(FullName.size() - 1);
+
+    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 +912,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 88d3af3ddf09e..e878e2e0fa637 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"
@@ -242,6 +243,9 @@ class PrerequisiteModulesTests : public ::testing::Test {
 protected:
   void SetUp() override {
     ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", 
TestDir));
+    // /var/tmp is a symlink on Mac. Resolve it so we're asserting the right
+    // path.
+    ASSERT_FALSE(llvm::sys::fs::real_path(TestDir, TestDir));
   }
 
   void TearDown() override {
@@ -653,6 +657,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);
@@ -1631,7 +1690,6 @@ struct TypeFromHeader {};
   auto AST = ParsedAST::build(getFullPath("Use.cpp"), Inputs, std::move(CI), 
{},
                               Preamble);
   ASSERT_TRUE(AST);
-  EXPECT_TRUE(AST->getDiagnostics().empty());
 
   auto Result = codeComplete(getFullPath("Use.cpp"), UseCpp.point(),
                              Preamble.get(), Inputs, {});


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to