[clang] [clang][Modules] Keep current-TU global-module-fragment internal-linkage functions viable in overload resolution (PR #210825)
David Brittain via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 21 13:47:33 PDT 2026
https://github.com/combinatorial updated https://github.com/llvm/llvm-project/pull/210825
>From fb78a6b2326248aeeedff057e008cbec06b973e9 Mon Sep 17 00:00:00 2001
From: David Brittain <david at tophatch.com>
Date: Mon, 20 Jul 2026 21:56:47 +0000
Subject: [PATCH] [clang][Modules] Keep current-TU global-module-fragment
internal-linkage functions viable in overload resolution
An internal-linkage function (e.g. a `static` or anonymous-namespace helper)
declared in the global module fragment of the current translation unit is
usable within the module unit ([module.global.frag]/p1). However,
Sema::AddOverloadCandidate removed such a function from the overload set
whenever Function->isInAnotherModuleUnit() was true. That predicate misfires
for a current-TU GMF declaration when the enclosing template is instantiated
after the global module fragment is closed: pending instantiations run in
ActOnEndOfTranslationUnitFragment, and isInAnotherModuleUnit() consults
ASTContext's current named module, which is not in scope yet at that point.
The candidate was then dropped with the hidden ovl_fail_module_mismatched
failure kind -- yielding a spurious "no matching function" error with no
candidate notes.
The existing carve-out only exempted inline / implicitly-instantiated GMF
functions (added in #104701), so a plain `static` helper reached through such
a template still failed.
Recognise a global-module-fragment function as belonging to the current unit
by comparing its top-level module against Sema::getCurrentModule() (the module
scope, which is still correct while the GMF is being finalised), and keep it in
the overload set. Uses of a `static` function genuinely imported from another
module unit remain subject to the internal-linkage rule.
Fixes #210822.
---
clang/docs/ReleaseNotes.md | 7 ++++
clang/lib/Sema/SemaOverload.cpp | 11 +++++-
.../gmf-internal-linkage-in-template.cppm | 37 +++++++++++++++++++
3 files changed, 54 insertions(+), 1 deletion(-)
create mode 100644 clang/test/Modules/gmf-internal-linkage-in-template.cppm
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 5a07e77076d17..dbbf858ad68be 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -323,6 +323,13 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
- Fixed a crash when a using-declaration naming an unresolvable member of a
dependent base was shadowed by an invalid using-declaration. (#GH209427)
+- Fixed a regression where an internal-linkage function (e.g. a `static` or
+ anonymous-namespace helper) declared in the global module fragment of the
+ current translation unit was removed from the overload set when the calling
+ template was instantiated after the global module fragment was closed,
+ producing a spurious "no matching function" error with no candidate notes.
+ (#GH210822)
+
#### Bug Fixes to AST Handling
- Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index dc912e8e95fc4..d66dea0d918fa 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -7388,7 +7388,16 @@ void Sema::AddOverloadCandidate(
Function->isFromGlobalModule() &&
(IsImplicitlyInstantiated || Function->isInlined());
- if (ND->getFormalLinkage() == Linkage::Internal && !IsInlineFunctionInGMF) {
+ // Don't exclude internal-linkage entities from the current TU's global
+ // module fragment.
+ const Module *CurrentModule = getCurrentModule();
+ const bool IsCurrentUnitGMFDecl =
+ Function->isFromGlobalModule() && CurrentModule &&
+ Function->getOwningModule()->getTopLevelModule() ==
+ CurrentModule->getTopLevelModule();
+
+ if (ND->getFormalLinkage() == Linkage::Internal && !IsInlineFunctionInGMF &&
+ !IsCurrentUnitGMFDecl) {
Candidate.Viable = false;
Candidate.FailureKind = ovl_fail_module_mismatched;
return;
diff --git a/clang/test/Modules/gmf-internal-linkage-in-template.cppm b/clang/test/Modules/gmf-internal-linkage-in-template.cppm
new file mode 100644
index 0000000000000..faa41fe2ba041
--- /dev/null
+++ b/clang/test/Modules/gmf-internal-linkage-in-template.cppm
@@ -0,0 +1,37 @@
+// Verify that an internal-linkage function declared in the global module
+// fragment of the current translation unit remains usable from within the
+// module unit when it is only reached through a template instantiation that is
+// performed after the global module fragment is closed (i.e. the instantiation
+// is triggered by another entity in the global module fragment).
+//
+// This is a regression test: such a plain 'static' (non-inline) helper used to
+// be wrongly removed from the overload set, producing a bogus "no matching
+// function" error with no candidate notes.
+//
+// RUN: %clang_cc1 -std=c++20 %s -emit-module-interface -o %t.pcm -verify
+// RUN: %clang_cc1 -std=c++23 %s -emit-module-interface -o %t.pcm -verify
+
+// expected-no-diagnostics
+
+module;
+static void slow(unsigned long *o) { *o = 0; }
+static void slow(unsigned int *o) { *o = 0; }
+
+template <typename T> void parse(T *o) { slow(o); }
+
+// These inline functions are in the global module fragment; the template
+// specializations they use are instantiated when the fragment is closed.
+inline unsigned long read64() {
+ unsigned long t;
+ parse(&t);
+ return t;
+}
+inline unsigned int read32() {
+ unsigned int t;
+ parse(&t);
+ return t;
+}
+
+export module a;
+
+export inline unsigned long use() { return read64() + read32(); }
More information about the cfe-commits
mailing list