Skip to content

Commit

Permalink
[Clang][Sema] Expose static inline functions from GMF (llvm#104701)
Browse files Browse the repository at this point in the history
In C, it is a common pattern to have `static inline` functions in
headers to avoid ODR issues. Currently, when those headers are included
in a GMF, the names are not found when two-phase name lookup and ADL is
involved. Those names are removed by `Sema::AddOverloadCandidate`.

Similarly, in C++, sometimes people use templates with internal linkage
in headers.

As the GMF was designed to be a transitional mechanism for headers,
special case those functions in `Sema::AddOverloadCandidate`.

This fixes <llvm#98021>.
  • Loading branch information
jiixyj authored Dec 31, 2024
1 parent fe1f64e commit e50ec3e
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 4 deletions.
23 changes: 19 additions & 4 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6977,11 +6977,26 @@ void Sema::AddOverloadCandidate(
/// have linkage. So that all entities of the same should share one
/// linkage. But in clang, different entities of the same could have
/// different linkage.
NamedDecl *ND = Function;
if (auto *SpecInfo = Function->getTemplateSpecializationInfo())
const NamedDecl *ND = Function;
bool IsImplicitlyInstantiated = false;
if (auto *SpecInfo = Function->getTemplateSpecializationInfo()) {
ND = SpecInfo->getTemplate();

if (ND->getFormalLinkage() == Linkage::Internal) {
IsImplicitlyInstantiated = SpecInfo->getTemplateSpecializationKind() ==
TSK_ImplicitInstantiation;
}

/// Don't remove inline functions with internal linkage from the overload
/// set if they are declared in a GMF, in violation of C++ [basic.link]p17.
/// However:
/// - Inline functions with internal linkage are a common pattern in
/// headers to avoid ODR issues.
/// - The global module is meant to be a transition mechanism for C and C++
/// headers, and the current rules as written work against that goal.
const bool IsInlineFunctionInGMF =
Function->isFromGlobalModule() &&
(IsImplicitlyInstantiated || Function->isInlined());

if (ND->getFormalLinkage() == Linkage::Internal && !IsInlineFunctionInGMF) {
Candidate.Viable = false;
Candidate.FailureKind = ovl_fail_module_mismatched;
return;
Expand Down
37 changes: 37 additions & 0 deletions clang/test/Modules/expose-static-inline-from-gmf-1.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm \
// RUN: -DTEST_INLINE
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify \
// RUN: -DTEST_INLINE
//
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify

//--- a.h
#ifdef TEST_INLINE
#define INLINE inline
#else
#define INLINE
#endif
static INLINE void func(long) {}
template <typename T = long> void a() { func(T{}); }

//--- a.cppm
module;
#include "a.h"
export module a;
export using ::a;

//--- test.cc
import a;
auto m = (a(), 0);

#ifdef TEST_INLINE
// expected-no-diagnostics
#else
// [email protected]:7 {{no matching function for call to 'func'}}
// [email protected]:2 {{in instantiation of function template specialization 'a<long>' requested here}}
#endif
22 changes: 22 additions & 0 deletions clang/test/Modules/expose-static-inline-from-gmf-2.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify

//--- a.h
template <typename G> static inline void func() {}
template <typename T = long> void a() { func<T>(); }

//--- a.cppm
module;
#include "a.h"
export module a;
export using ::a;

//--- test.cc
import a;
auto m = (a(), 0);

// expected-no-diagnostics
24 changes: 24 additions & 0 deletions clang/test/Modules/expose-static-inline-from-gmf-3.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify

//--- a.h
namespace ns {
template <typename G> static void func() {}
template <typename T = long> void a() { func<T>(); }
}

//--- a.cppm
module;
#include "a.h"
export module a;
export using ns::a;

//--- test.cc
import a;
auto m = (a(), 0);

// expected-no-diagnostics
40 changes: 40 additions & 0 deletions clang/test/Modules/expose-static-inline-from-gmf-4.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm \
// RUN: -DTEST_INLINE
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify \
// RUN: -DTEST_INLINE
//
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify

//--- a.h
#ifdef TEST_INLINE
#define INLINE inline
#else
#define INLINE
#endif
namespace ns {
template <typename G> static void func() {}
template <> INLINE void func<long>() {}
template <typename T = long> void a() { func<T>(); }
}

//--- a.cppm
module;
#include "a.h"
export module a;
export using ns::a;

//--- test.cc
import a;
auto m = (a(), 0);

#ifdef TEST_INLINE
// expected-no-diagnostics
#else
// [email protected]:9 {{no matching function for call to 'func'}}
// [email protected]:2 {{in instantiation of function template specialization 'ns::a<long>' requested here}}
#endif
26 changes: 26 additions & 0 deletions clang/test/Modules/expose-static-inline-from-gmf-5.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify

//--- a.h
namespace ns {
namespace {
template <typename G> void func() {}
}
template <typename T = long> void a() { func<T>(); }
}

//--- a.cppm
module;
#include "a.h"
export module a;
export using ns::a;

//--- test.cc
import a;
auto m = (a(), 0);

// expected-no-diagnostics

0 comments on commit e50ec3e

Please sign in to comment.