Skip to content

Release/20.x clangd modules #143647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions clang-tools-extra/clangd/ModulesBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,9 @@ void ModuleFileCache::remove(StringRef ModuleName) {
/// Collect the directly and indirectly required module names for \param
/// ModuleName in topological order. The \param ModuleName is guaranteed to
/// be the last element in \param ModuleNames.
llvm::SmallVector<StringRef> getAllRequiredModules(ProjectModules &MDB,
llvm::SmallVector<std::string> getAllRequiredModules(ProjectModules &MDB,
StringRef ModuleName) {
llvm::SmallVector<llvm::StringRef> ModuleNames;
llvm::SmallVector<std::string> ModuleNames;
llvm::StringSet<> ModuleNamesSet;

auto VisitDeps = [&](StringRef ModuleName, auto Visitor) -> void {
Expand All @@ -373,7 +373,7 @@ llvm::SmallVector<StringRef> getAllRequiredModules(ProjectModules &MDB,
if (ModuleNamesSet.insert(RequiredModuleName).second)
Visitor(RequiredModuleName, Visitor);

ModuleNames.push_back(ModuleName);
ModuleNames.push_back(ModuleName.str());
};
VisitDeps(ModuleName, VisitDeps);

Expand Down Expand Up @@ -418,28 +418,30 @@ llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile(
// Get Required modules in topological order.
auto ReqModuleNames = getAllRequiredModules(MDB, ModuleName);
for (llvm::StringRef ReqModuleName : ReqModuleNames) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
if (BuiltModuleFiles.isModuleUnitBuilt(ReqModuleName))
continue;

if (auto Cached = Cache.getModule(ReqModuleName)) {
if (IsModuleFileUpToDate(Cached->getModuleFilePath(), BuiltModuleFiles,
TFS.view(std::nullopt))) {
log("Reusing module {0} from {1}", ModuleName,
log("Reusing module {0} from {1}", ReqModuleName,
Cached->getModuleFilePath());
BuiltModuleFiles.addModuleFile(std::move(Cached));
continue;
}
Cache.remove(ReqModuleName);
}

std::string ReqFileName =
MDB.getSourceForModuleName(ReqModuleName);
llvm::Expected<ModuleFile> MF = buildModuleFile(
ModuleName, ModuleUnitFileName, getCDB(), TFS, BuiltModuleFiles);
ReqModuleName, ReqFileName, getCDB(), TFS, BuiltModuleFiles);
if (llvm::Error Err = MF.takeError())
return Err;

log("Built module {0} to {1}", ModuleName, MF->getModuleFilePath());
log("Built module {0} to {1}", ReqModuleName, MF->getModuleFilePath());
auto BuiltModuleFile = std::make_shared<const ModuleFile>(std::move(*MF));
Cache.add(ModuleName, BuiltModuleFile);
Cache.add(ReqModuleName, BuiltModuleFile);
BuiltModuleFiles.addModuleFile(std::move(BuiltModuleFile));
}

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/ProjectModules.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class ProjectModules {
llvm::unique_function<void(tooling::CompileCommand &, PathRef) const>;

virtual std::vector<std::string> getRequiredModules(PathRef File) = 0;
virtual PathRef
virtual std::string
getSourceForModuleName(llvm::StringRef ModuleName,
PathRef RequiredSrcFile = PathRef()) = 0;

Expand Down
6 changes: 3 additions & 3 deletions clang-tools-extra/clangd/ScanningProjectModules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class ModuleDependencyScanner {
///
/// TODO: We should handle the case that there are multiple source files
/// declaring the same module.
PathRef getSourceForModuleName(llvm::StringRef ModuleName) const;
std::string getSourceForModuleName(llvm::StringRef ModuleName) const;

/// Return the direct required modules. Indirect required modules are not
/// included.
Expand Down Expand Up @@ -140,7 +140,7 @@ void ModuleDependencyScanner::globalScan(
GlobalScanned = true;
}

PathRef ModuleDependencyScanner::getSourceForModuleName(
std::string ModuleDependencyScanner::getSourceForModuleName(
llvm::StringRef ModuleName) const {
assert(
GlobalScanned &&
Expand Down Expand Up @@ -189,7 +189,7 @@ class ScanningAllProjectModules : public ProjectModules {

/// RequiredSourceFile is not used intentionally. See the comments of
/// ModuleDependencyScanner for detail.
PathRef
std::string
getSourceForModuleName(llvm::StringRef ModuleName,
PathRef RequiredSourceFile = PathRef()) override {
Scanner.globalScan(Mangler);
Expand Down
95 changes: 95 additions & 0 deletions clang-tools-extra/clangd/test/module_dependencies.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# A smoke test to check that a simple dependency chain for modules can work.
#
# FIXME: The test fails on Windows; see comments on https://github.com/llvm/llvm-project/pull/142828
# UNSUPPORTED: system-windows
#
# RUN: rm -fr %t
# RUN: mkdir -p %t
# RUN: split-file %s %t
#
# RUN: sed -e "s|DIR|%/t|g" %t/compile_commands.json.tmpl > %t/compile_commands.json.tmp
# RUN: sed -e "s|CLANG_CC|%clang|g" %t/compile_commands.json.tmp > %t/compile_commands.json
# RUN: sed -e "s|DIR|%/t|g" %t/definition.jsonrpc.tmpl > %t/definition.jsonrpc.tmp
#
# On Windows, we need the URI in didOpen to look like "uri":"file:///C:/..."
# (with the extra slash in the front), so we add it here.
# RUN: sed -E -e 's|"file://([A-Z]):/|"file:///\1:/|g' %/t/definition.jsonrpc.tmp > %/t/definition.jsonrpc
#
# RUN: clangd -experimental-modules-support -lit-test < %t/definition.jsonrpc \
# RUN: | FileCheck -strict-whitespace %t/definition.jsonrpc

#--- A-frag.cppm
export module A:frag;
export void printA() {}

#--- A.cppm
export module A;
export import :frag;

#--- Use.cpp
import A;
void foo() {
print
}

#--- compile_commands.json.tmpl
[
{
"directory": "DIR",
"command": "CLANG_CC -fprebuilt-module-path=DIR -std=c++20 -o DIR/main.cpp.o -c DIR/Use.cpp",
"file": "DIR/Use.cpp"
},
{
"directory": "DIR",
"command": "CLANG_CC -std=c++20 DIR/A.cppm --precompile -o DIR/A.pcm",
"file": "DIR/A.cppm"
},
{
"directory": "DIR",
"command": "CLANG_CC -std=c++20 DIR/A-frag.cppm --precompile -o DIR/A-frag.pcm",
"file": "DIR/A-frag.cppm"
}
]

#--- definition.jsonrpc.tmpl
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"processId": 123,
"rootPath": "clangd",
"capabilities": {
"textDocument": {
"completion": {
"completionItem": {
"snippetSupport": true
}
}
}
},
"trace": "off"
}
}
---
{
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": {
"textDocument": {
"uri": "file://DIR/Use.cpp",
"languageId": "cpp",
"version": 1,
"text": "import A;\nvoid foo() {\n print\n}\n"
}
}
}

# CHECK: "message"{{.*}}printA{{.*}}(fix available)

---
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file://DIR/Use.cpp"},"context":{"triggerKind":1},"position":{"line":2,"character":6}}}
---
{"jsonrpc":"2.0","id":2,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
Loading