[cxx-interop] Support _LIBCPP_PREFERRED_OVERLOAD

Some functions like memchr are defined both in libc++ and libc.
Including both would result in ambiguous references at the call sites.
This is worked around by an attribute that tells the compiler to prefer
one overload over the others. This attribute was not interpreted by
Swift. As a result, importing both libc and libc++ and calling such
functions resulted in compilation errors due to ambiguous overloads.
This PR modifies the lookup logic to exclude the non-preferred Clang
functions from the overload set whenever a preferred overload is
available.

rdar://152192945
This commit is contained in:
Gabor Horvath
2025-06-05 14:44:45 +01:00
parent 68524a8a62
commit 27879c7bfa
6 changed files with 76 additions and 0 deletions

View File

@@ -17,6 +17,8 @@
#include "swift/AST/NameLookup.h"
#include "swift/AST/NameLookupRequests.h"
#include "swift/Basic/Assertions.h"
#include "clang/AST/Attr.h"
#include "clang/AST/ExprCXX.h"
#include "llvm/Support/raw_ostream.h"
using namespace swift;
@@ -278,6 +280,43 @@ void ModuleNameLookup<LookupStrategy>::lookupInModule(
if (decls.size() - initialCount <= 1)
return;
// Some functions like `memchr` are defined both in libc and in libc++.
// Importing both would result in ambiguities, but there are some attributes
// that mark the preferred overloads. See _LIBCPP_PREFERRED_OVERLOAD.
llvm::SmallPtrSet<ValueDecl *, 4> declsToRemove;
bool hasPreferredOverload = false;
for (auto decl : decls)
if (const auto *clangDecl = decl->getClangDecl()) {
if (clangDecl->hasAttr<clang::EnableIfAttr>()) {
// FIXME: at some point we might want to call into Clang to implement
// the full enable_if semantics including the constant evaluation of the
// conditions. For now, just look for the first enable_if(true, "...")
// and assume all the rest of the enable_ifs evaluate to true.
bool thisDeclHasPreferredOverload = false;
for (auto clangAttr :
clangDecl->specific_attrs<clang::EnableIfAttr>()) {
if (auto litExpr =
dyn_cast<clang::CXXBoolLiteralExpr>(clangAttr->getCond())) {
if (litExpr->getValue()) {
thisDeclHasPreferredOverload = hasPreferredOverload = true;
break;
}
}
}
if (!thisDeclHasPreferredOverload)
declsToRemove.insert(decl);
} else
declsToRemove.insert(decl);
}
if (hasPreferredOverload) {
decls.erase(std::remove_if(decls.begin() + initialCount, decls.end(),
[&](ValueDecl *d) -> bool {
return declsToRemove.contains(d);
}),
decls.end());
}
// Remove duplicated declarations, which can happen when the same module is
// imported with multiple access paths.
llvm::SmallPtrSet<ValueDecl *, 4> knownDecls;