[clang] [Modules][Diagnostic] Improve diagnostics for stale module dependencies (PR #167713)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jan 21 00:50:17 PST 2026
https://github.com/devajithvs updated https://github.com/llvm/llvm-project/pull/167713
>From 4402af34bc867dd67e8e5237afa8da1c2df397c6 Mon Sep 17 00:00:00 2001
From: Devajith Valaparambil Sreeramaswamy
<devajith.valaparambil.sreeramaswamy at cern.ch>
Date: Tue, 11 Nov 2025 15:49:12 +0100
Subject: [PATCH] [Modules][Diagnostic] Improve diagnostics for stale module
dependencies
When a module becomes out of date because its dependency has changed,
Clang previously reported that the dependency itself was `out of date and
needs to be rebuilt`, even when the dependency had just been rebuilt. This
was confusing because the real issue is that the importing module is stale
due to the dependency change.
This patch introduces a new diagnostic that clearly indicates which module
is out of date and which dependency has changed, making it easier for users
to understand what needs to be rebuilt.
Before:
`module file 'A.pcm' is out of date and needs to be rebuilt`
(even though A.pcm was just rebuilt)
After:
`module file 'B.pcm' is out of date because dependency 'A.pcm' has changed`
---
.../Basic/DiagnosticSerializationKinds.td | 3 ++
.../clang/Serialization/ModuleManager.h | 7 +++-
clang/lib/Serialization/ASTReader.cpp | 16 ++++++-
clang/lib/Serialization/ModuleManager.cpp | 6 +--
clang/test/Modules/explicit-build.cpp | 42 ++++++++++++++++++-
clang/test/Modules/invalid-module-dep.c | 2 +-
6 files changed, 68 insertions(+), 8 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSerializationKinds.td b/clang/include/clang/Basic/DiagnosticSerializationKinds.td
index b80aff385e01f..d79fbcd9b8a84 100644
--- a/clang/include/clang/Basic/DiagnosticSerializationKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSerializationKinds.td
@@ -71,6 +71,9 @@ def err_ast_file_not_found : Error<
def err_ast_file_out_of_date : Error<
"%select{PCH|module|precompiled}0 file '%1' is out of date and "
"needs to be rebuilt%select{|: %3}2">, DefaultFatal;
+def err_ast_file_dependency_out_of_date : Error<
+ "%select{PCH|module|AST}0 file '%1' is out of date because "
+ "dependency '%2' has changed%select{|: %4}3">, DefaultFatal;
def err_ast_file_invalid : Error<
"file '%1' is not a valid %select{PCH|module|precompiled}0 file: %2">, DefaultFatal;
def note_module_file_imported_by : Note<
diff --git a/clang/include/clang/Serialization/ModuleManager.h b/clang/include/clang/Serialization/ModuleManager.h
index 1eb74aee9787c..275cf36866294 100644
--- a/clang/include/clang/Serialization/ModuleManager.h
+++ b/clang/include/clang/Serialization/ModuleManager.h
@@ -198,8 +198,11 @@ class ModuleManager {
/// The module file is missing.
Missing,
- /// The module file is out-of-date.
- OutOfDate
+ /// The importee module file is out-of-date.
+ ImporteeOutOfDate,
+
+ /// The importer file is out-of-date
+ ImporterOutOfDate
};
using ASTFileSignatureReader = ASTFileSignature (*)(StringRef);
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 634bf991b2aee..0d4481e1ceb38 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -5128,7 +5128,7 @@ ASTReader::ReadASTCore(StringRef FileName,
<< ErrorStr;
return Failure;
- case ModuleManager::OutOfDate:
+ case ModuleManager::ImporteeOutOfDate:
// We couldn't load the module file because it is out-of-date. If the
// client can handle out-of-date, return it.
if (ClientLoadCapabilities & ARR_OutOfDate)
@@ -5139,6 +5139,20 @@ ASTReader::ReadASTCore(StringRef FileName,
<< moduleKindForDiagnostic(Type) << FileName << !ErrorStr.empty()
<< ErrorStr;
return Failure;
+
+ case ModuleManager::ImporterOutOfDate:
+ // We couldn't load the module file because it is out-of-date. If the
+ // client can handle out-of-date, return it.
+ if (ClientLoadCapabilities & ARR_OutOfDate)
+ return OutOfDate;
+
+ // Otherwise, report that the importer is out of date because the dependency
+ // changed.
+ if (ImportedBy)
+ Diag(diag::err_ast_file_dependency_out_of_date)
+ << moduleKindForDiagnostic(ImportedBy->Kind) << ImportedBy->FileName
+ << FileName << !ErrorStr.empty() << StringRef(ErrorStr);
+ return Failure;
}
assert(M && "Missing module file");
diff --git a/clang/lib/Serialization/ModuleManager.cpp b/clang/lib/Serialization/ModuleManager.cpp
index 482c17649ed55..e53ae7f6593ad 100644
--- a/clang/lib/Serialization/ModuleManager.cpp
+++ b/clang/lib/Serialization/ModuleManager.cpp
@@ -125,7 +125,7 @@ ModuleManager::addModule(StringRef FileName, ModuleKind Type,
ErrorStr = IgnoreModTime ? "module file has a different size than expected"
: "module file has a different size or "
"modification time than expected";
- return OutOfDate;
+ return ImporterOutOfDate;
}
if (!Entry) {
@@ -159,7 +159,7 @@ ModuleManager::addModule(StringRef FileName, ModuleKind Type,
if (implicitModuleNamesMatch(Type, ModuleEntry, *Entry)) {
// Check the stored signature.
if (checkSignature(ModuleEntry->Signature, ExpectedSignature, ErrorStr))
- return OutOfDate;
+ return ImporterOutOfDate;
Module = ModuleEntry;
updateModuleImports(*ModuleEntry, ImportedBy, ImportLoc);
@@ -226,7 +226,7 @@ ModuleManager::addModule(StringRef FileName, ModuleKind Type,
// ReadSignature unless there's something to check though.
if (ExpectedSignature && checkSignature(ReadSignature(NewModule->Data),
ExpectedSignature, ErrorStr))
- return OutOfDate;
+ return ImporterOutOfDate;
// We're keeping this module. Store it everywhere.
Module = Modules[*Entry] = NewModule.get();
diff --git a/clang/test/Modules/explicit-build.cpp b/clang/test/Modules/explicit-build.cpp
index c2fe2024a3629..3e86c8138362f 100644
--- a/clang/test/Modules/explicit-build.cpp
+++ b/clang/test/Modules/explicit-build.cpp
@@ -184,6 +184,46 @@
// CHECK-NO-FILE-INDIRECT-NEXT: note: imported by module 'c' in '{{.*}}c.pcm'
// CHECK-NO-FILE-INDIRECT-NOT: note:
+// -------------------------------
+// Check that we diagnose stale dependencies correctly when modules change.
+//
+// Trigger a rebuild of A with a different configuration (-DA_EXTRA_DEFINE) to make B and C out of date
+// RUN: mv %t/a.pcm %t/a-tmp.pcm
+// RUN: %clang_cc1 -x c++ -std=c++11 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -Rmodule-build -fno-modules-error-recovery \
+// RUN: -fmodule-name=a -emit-module %S/Inputs/explicit-build/module.modulemap -o %t/a.pcm \
+// RUN: -DA_EXTRA_DEFINE \
+// RUN: 2>&1 | FileCheck --check-prefix=CHECK-NO-IMPLICIT-BUILD %s --allow-empty
+
+// Try to use C, which depends on B, which depends on the now-changed A.
+// RUN: not %clang_cc1 -x c++ -std=c++11 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -Rmodule-build -fno-modules-error-recovery \
+// RUN: -I%S/Inputs/explicit-build \
+// RUN: -fmodule-file=%t/c.pcm \
+// RUN: %s -DHAVE_A -DHAVE_B -DHAVE_C 2>&1 | FileCheck --check-prefix=CHECK-OUT-OF-DATE-INDIRECT %s
+//
+// CHECK-OUT-OF-DATE-INDIRECT: fatal error: module file '{{.*}}b.pcm' is out of date because dependency '{{.*}}a.pcm' has changed
+// CHECK-OUT-OF-DATE-INDIRECT-NEXT: note: imported by module 'b' in '{{.*}}b.pcm'
+// CHECK-OUT-OF-DATE-INDIRECT-NEXT: note: imported by module 'c' in '{{.*}}c.pcm'
+
+// Rebuild B with the new A, leaving C out of date.
+// RUN: mv %t/b.pcm %t/b-tmp.pcm
+// RUN: %clang_cc1 -x c++ -std=c++11 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -Rmodule-build -fno-modules-error-recovery \
+// RUN: -fmodule-file=%t/a.pcm \
+// RUN: -fmodule-name=b -emit-module %S/Inputs/explicit-build/module.modulemap -o %t/b.pcm \
+// RUN: -DA_EXTRA_DEFINE \
+// RUN: 2>&1 | FileCheck --check-prefix=CHECK-NO-IMPLICIT-BUILD %s --allow-empty
+//
+// Now only C is out of date. Try to use C.
+// RUN: not %clang_cc1 -x c++ -std=c++11 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -Rmodule-build -fno-modules-error-recovery \
+// RUN: -I%S/Inputs/explicit-build \
+// RUN: -fmodule-file=%t/c.pcm \
+// RUN: %s -DHAVE_A -DHAVE_B -DHAVE_C 2>&1 | FileCheck --check-prefix=CHECK-OUT-OF-DATE-DIRECT %s
+//
+// CHECK-OUT-OF-DATE-DIRECT: fatal error: module file '{{.*}}c.pcm' is out of date because dependency '{{.*}}b.pcm' has changed
+// CHECK-OUT-OF-DATE-DIRECT-NOT: fatal error: module file '{{.*}}b.pcm' is out of date
+//
+// RUN: mv %t/a-tmp.pcm %t/a.pcm
+// RUN: mv %t/b-tmp.pcm %t/b.pcm
+
// -------------------------------
// Check that we don't get upset if B's timestamp is newer than C's.
// RUN: touch %t/b.pcm
@@ -202,6 +242,6 @@
// RUN: -fmodule-file=%t/c.pcm \
// RUN: %s -DHAVE_A -DHAVE_B -DHAVE_C 2>&1 | FileCheck --check-prefix=CHECK-MISMATCHED-B %s
//
-// CHECK-MISMATCHED-B: fatal error: module file '{{.*}}b.pcm' is out of date and needs to be rebuilt: module file has a different size than expected
+// CHECK-MISMATCHED-B: fatal error: module file '{{.*}}c.pcm' is out of date because dependency '{{.*}}b.pcm' has changed: module file has a different size than expected
// CHECK-MISMATCHED-B-NEXT: note: imported by module 'c'
// CHECK-MISMATCHED-B-NOT: note:
diff --git a/clang/test/Modules/invalid-module-dep.c b/clang/test/Modules/invalid-module-dep.c
index 2bcda8be5f1b6..70496fd83db38 100644
--- a/clang/test/Modules/invalid-module-dep.c
+++ b/clang/test/Modules/invalid-module-dep.c
@@ -37,7 +37,7 @@
#include <A/A.h>
#include <B/B.h> // expected-warning {{conflicts with imported file}} \
// expected-note {{imported by module 'B'}} \
- // expected-error {{out of date and needs to be rebuilt}}
+ // expected-error {{is out of date because dependency}}
//--- Sysroot/usr/include/A/module.modulemap
module A [system] {
More information about the cfe-commits
mailing list