[clang-tools-extra] [clang-tidy] Make `misc-use-internal-linkage` not diagnose symbols in importable module units (PR #188679)
Victor Chernyakin via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 25 23:59:32 PDT 2026
https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/188679
>From 32fdcb4627d00e280db2f279ff9dc337b94ddec9 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Wed, 25 Mar 2026 20:02:56 -0700
Subject: [PATCH 1/2] [clang-tidy] Make `misc-use-internal-linkage` more
conservative in module interface units
---
.../misc/UseInternalLinkageCheck.cpp | 21 ++++++++-----
clang-tools-extra/docs/ReleaseNotes.rst | 4 +++
clang-tools-extra/test/CMakeLists.txt | 3 ++
...internal-linkage-module-implementation.cpp | 31 +++++++++++++++++++
.../use-internal-linkage-module-partition.cpp | 10 ++++++
.../misc/use-internal-linkage-module.cpp | 21 ++++++++++++-
clang-tools-extra/test/lit.cfg.py | 4 +--
7 files changed, 84 insertions(+), 10 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-implementation.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-partition.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp b/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
index 68115cb28e7c8..3aa4b0809aa98 100644
--- a/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
@@ -12,6 +12,7 @@
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/Basic/Module.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Lex/Token.h"
@@ -65,6 +66,14 @@ AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); }
AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); }
+AST_MATCHER(Decl, isInImportableModuleUnit) {
+ if (const Module *OwningModule = Node.getOwningModule())
+ if (OwningModule->Kind != Module::ModuleImplementationUnit &&
+ OwningModule->Kind != Module::PrivateModuleFragment)
+ return true;
+ return false;
+}
+
AST_MATCHER_P(Decl, isAllRedeclsInMainFile, FileExtensionsSet,
HeaderFileExtensions) {
return llvm::all_of(Node.redecls(), [&](const Decl *D) {
@@ -136,13 +145,9 @@ void UseInternalLinkageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
void UseInternalLinkageCheck::registerMatchers(MatchFinder *Finder) {
auto Common =
allOf(isFirstDecl(), isAllRedeclsInMainFile(HeaderFileExtensions),
- unless(anyOf(
- // 1. internal linkage
- isInAnonymousNamespace(), hasAncestor(decl(anyOf(
- // 2. friend
- friendDecl(),
- // 3. module export decl
- exportDecl()))))));
+ unless(anyOf(isInAnonymousNamespace(), isInImportableModuleUnit(),
+ hasAncestor(decl(friendDecl())))));
+
if (AnalyzeFunctions)
Finder->addMatcher(
functionDecl(
@@ -154,6 +159,7 @@ void UseInternalLinkageCheck::registerMatchers(MatchFinder *Finder) {
isAllocationOrDeallocationOverloadedFunction(), isMain())))
.bind("fn"),
this);
+
if (AnalyzeVariables)
Finder->addMatcher(
varDecl(Common, hasGlobalStorage(),
@@ -163,6 +169,7 @@ void UseInternalLinkageCheck::registerMatchers(MatchFinder *Finder) {
hasThreadStorageDuration())))
.bind("var"),
this);
+
if (getLangOpts().CPlusPlus && AnalyzeTypes)
Finder->addMatcher(
tagDecl(Common, isDefinition(), hasNameForLinkage(),
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index daf635bf625e7..eacebb55f8c66 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -297,6 +297,10 @@ Changes in existing checks
<clang-tidy/checks/misc/unused-using-decls>` to not diagnose ``using``
declarations as unused if they're exported from a module.
+- Improved :doc:`misc-use-internal-linkage
+ <clang-tidy/checks/misc/use-internal-linkage>` to not suggest giving
+ internal linkage to entities defined in C++ module interface units.
+
- Improved :doc:`modernize-pass-by-value
<clang-tidy/checks/modernize/pass-by-value>` check by adding `IgnoreMacros`
option to suppress warnings in macros.
diff --git a/clang-tools-extra/test/CMakeLists.txt b/clang-tools-extra/test/CMakeLists.txt
index 97c7a66cd69fd..22f227a891f82 100644
--- a/clang-tools-extra/test/CMakeLists.txt
+++ b/clang-tools-extra/test/CMakeLists.txt
@@ -44,6 +44,9 @@ set(CLANG_TOOLS_TEST_DEPS
clang-resource-headers
clang-tidy
+
+ # Some tests invoke clang directly (e.g., to precompile modules).
+ clang
)
# Add lit test dependencies.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-implementation.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-implementation.cpp
new file mode 100644
index 0000000000000..9c4e00b3cf306
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-implementation.cpp
@@ -0,0 +1,31 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+// RUN: %clang -std=c++20 --precompile %t/foo.cppm -o %t/foo.pcm
+// RUN: %check_clang_tidy -std=c++20 %t/foo.cppm misc-use-internal-linkage %t/out
+// RUN: %check_clang_tidy -std=c++20 %t/foo.cpp misc-use-internal-linkage %t/out \
+// RUN: -- -- -fmodule-file=foo=%t/foo.pcm
+
+//--- foo.cppm
+
+export module foo;
+
+export void exported_fn();
+export extern int exported_var;
+export struct ExportedStruct;
+
+//--- foo.cpp
+module foo;
+
+void exported_fn() {}
+int exported_var;
+struct ExportedStruct {};
+
+void internal_fn() {}
+// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'internal_fn' can be made static or moved into an anonymous namespace to enforce internal linkage
+// CHECK-FIXES: static void internal_fn() {}
+int internal_var;
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: variable 'internal_var' can be made static or moved into an anonymous namespace to enforce internal linkage
+// CHECK-FIXES: static int internal_var;
+struct InternalStruct {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: struct 'InternalStruct' can be moved into an anonymous namespace to enforce internal linkage
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-partition.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-partition.cpp
new file mode 100644
index 0000000000000..8dca5bc2700c0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module-partition.cpp
@@ -0,0 +1,10 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s misc-use-internal-linkage %t
+
+// Symbols in a partition are visible to any TU in the same module
+// that imports that partition, so we shouldn't warn on them.
+
+module foo:bar;
+
+void fn_in_partition() {}
+int var_in_partition;
+struct StructInPartition {};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp
index ae1dc359c7ad8..9ac99bbe6e5d4 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp
@@ -1,7 +1,11 @@
-// RUN: %check_clang_tidy -std=c++20-or-later %s misc-use-internal-linkage %t -- -- -I%S/Inputs/use-internal-linkage
+// RUN: %check_clang_tidy -std=c++20-or-later %s misc-use-internal-linkage %t
module;
+void fn_in_global_module_fragment() {}
+int var_in_global_module_fragment;
+struct StructInGlobalModuleFragment {};
+
export module test;
export void single_export_fn() {}
@@ -22,3 +26,18 @@ void namespace_export_fn() {}
int namespace_export_var;
struct NamespaceExportStruct {};
} // namespace aa
+
+void unexported_fn() {}
+int unexported_var;
+struct UnexportedStruct {};
+
+module : private;
+
+void fn_in_private_module_fragment() {}
+// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'fn_in_private_module_fragment' can be made static or moved into an anonymous namespace to enforce internal linkage
+// CHECK-FIXES: static void fn_in_private_module_fragment() {}
+int var_in_private_module_fragment;
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: variable 'var_in_private_module_fragment' can be made static or moved into an anonymous namespace to enforce internal linkage
+// CHECK-FIXES: static int var_in_private_module_fragment;
+struct StructInPrivateModuleFragment {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: struct 'StructInPrivateModuleFragment' can be moved into an anonymous namespace to enforce internal linkage
diff --git a/clang-tools-extra/test/lit.cfg.py b/clang-tools-extra/test/lit.cfg.py
index c39ea29329674..a3901d6e3f5c9 100644
--- a/clang-tools-extra/test/lit.cfg.py
+++ b/clang-tools-extra/test/lit.cfg.py
@@ -49,8 +49,8 @@
# test_exec_root: The root path where tests should be run.
config.test_exec_root = os.path.join(config.clang_tools_binary_dir, "test")
-# Tools need the same environment setup as clang (we don't need clang itself).
-llvm_config.clang_setup()
+# Set up clang for use in tests. Makes %clang substitution available.
+llvm_config.use_clang()
if config.clang_tidy_staticanalyzer:
config.available_features.add("static-analyzer")
>From c84de11600468f2484723adb54c3a942dc59aa4c Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Wed, 25 Mar 2026 23:59:18 -0700
Subject: [PATCH 2/2] Address feedback
---
.../clang-tidy/misc/UseInternalLinkageCheck.cpp | 5 +++--
clang-tools-extra/docs/ReleaseNotes.rst | 2 ++
.../checkers/misc/use-internal-linkage-module.cpp | 6 ------
3 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp b/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
index 3aa4b0809aa98..3c026c8faa37b 100644
--- a/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
@@ -68,8 +68,9 @@ AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); }
AST_MATCHER(Decl, isInImportableModuleUnit) {
if (const Module *OwningModule = Node.getOwningModule())
- if (OwningModule->Kind != Module::ModuleImplementationUnit &&
- OwningModule->Kind != Module::PrivateModuleFragment)
+ if (OwningModule->Kind == Module::ModuleInterfaceUnit ||
+ OwningModule->Kind == Module::ModulePartitionInterface ||
+ OwningModule->Kind == Module::ModulePartitionImplementation)
return true;
return false;
}
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index eacebb55f8c66..a83ef9a33aaaf 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -300,6 +300,8 @@ Changes in existing checks
- Improved :doc:`misc-use-internal-linkage
<clang-tidy/checks/misc/use-internal-linkage>` to not suggest giving
internal linkage to entities defined in C++ module interface units.
+ Because it only sees one file at a time, the check can't be sure
+ such entities aren't referenced in any other files of that module.
- Improved :doc:`modernize-pass-by-value
<clang-tidy/checks/modernize/pass-by-value>` check by adding `IgnoreMacros`
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp
index 9ac99bbe6e5d4..0a64588135313 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-internal-linkage-module.cpp
@@ -1,11 +1,5 @@
// RUN: %check_clang_tidy -std=c++20-or-later %s misc-use-internal-linkage %t
-module;
-
-void fn_in_global_module_fragment() {}
-int var_in_global_module_fragment;
-struct StructInGlobalModuleFragment {};
-
export module test;
export void single_export_fn() {}
More information about the cfe-commits
mailing list