[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
Mon Apr 6 23:57:14 PDT 2026


https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/188679

>From 9dd519bb77cf2ad54125f286988b9a89f2a1832a 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 +++
 ...internal-linkage-module-implementation.cpp | 31 +++++++++++++++++++
 .../use-internal-linkage-module-partition.cpp | 10 ++++++
 .../misc/use-internal-linkage-module.cpp      | 21 ++++++++++++-
 5 files changed, 79 insertions(+), 8 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 2558d5ea46d0d..9db1cd2c501f7 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -341,6 +341,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/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

>From 1b2d9be7261b2d05e2153a4cf1150f4ecad9184d 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 9db1cd2c501f7..0872bd1d0a313 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -344,6 +344,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