diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -465,6 +465,7 @@ bool isInAnonymousNamespace() const; bool isInStdNamespace() const; + bool isInStdRangesNamespace() const; ASTContext &getASTContext() const LLVM_READONLY; @@ -1986,6 +1987,7 @@ bool isNamespace() const { return getDeclKind() == Decl::Namespace; } bool isStdNamespace() const; + bool isStdRangesNamespace() const; bool isInlineNamespace() const; diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -396,6 +396,11 @@ return DC && DC->isStdNamespace(); } +bool Decl::isInStdRangesNamespace() const { + const DeclContext *DC = getDeclContext(); + return DC && DC->isStdRangesNamespace(); +} + TranslationUnitDecl *Decl::getTranslationUnitDecl() { if (auto *TUD = dyn_cast(this)) return TUD; @@ -1149,6 +1154,19 @@ return II && II->isStr("std"); } +bool DeclContext::isStdRangesNamespace() const { + if (!isNamespace()) + return false; + + const auto *ND = cast(this); + if (!ND->getParent()->isStdNamespace()) { + return false; + } + + const IdentifierInfo *II = ND->getIdentifier(); + return II && II->isStr("ranges"); +} + bool DeclContext::isDependentContext() const { if (isFileContext()) return false; diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -9452,6 +9452,12 @@ for (ADLResult::iterator I = Fns.begin(), E = Fns.end(); I != E; ++I) { DeclAccessPair FoundDecl = DeclAccessPair::make(*I, AS_none); + // Functions in 'std::ranges' are hidden from ADL per [range.iter.ops]/2 and + // [algorithms.requirements]/2. + if ((*I)->isInStdRangesNamespace() && + Name.getNameKind() == DeclarationName::NameKind::Identifier) + continue; + if (FunctionDecl *FD = dyn_cast(*I)) { if (ExplicitTemplateArgs) continue; @@ -9650,7 +9656,7 @@ const OverloadCandidate &Cand1, const OverloadCandidate &Cand2) { // FIXME: Per P2113R0 we also need to compare the template parameter lists - // when comparing template functions. + // when comparing template functions. if (Cand1.Function && Cand2.Function && Cand1.Function->hasPrototype() && Cand2.Function->hasPrototype()) { auto *PT1 = cast(Cand1.Function->getFunctionType()); @@ -12828,6 +12834,12 @@ CandidateSet, PartialOverloading, /*KnownValid*/ true); + // Functions in 'std::ranges' inhibit ADL per [range.iter.ops]/2 and + // [algorithms.requirements]/2. + if (!ULE->decls().empty() && ULE->decls_begin()->isInStdRangesNamespace() && + ULE->getName().getNameKind() == DeclarationName::NameKind::Identifier) + return; + if (ULE->requiresADL()) AddArgumentDependentLookupCandidates(ULE->getName(), ULE->getExprLoc(), Args, ExplicitTemplateArgs, diff --git a/clang/test/SemaCXX/disable-adl.cpp b/clang/test/SemaCXX/disable-adl.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/disable-adl.cpp @@ -0,0 +1,94 @@ +// RUN: %clang_cc1 %s -fsyntax-only -verify + +// expected-note@disable-adl.cpp:* 2{{}} + +namespace std { + struct S1 {}; + S1 inhibited(S1); + + namespace ranges { + struct S2 {}; + void hidden(S2); + int inhibited(S1); + } +} + +void test_functions() { + hidden(std::ranges::S2{}); + // expected-error@-1{{use of undeclared identifier 'hidden'; did you mean 'std::ranges::hidden'?}} + + using namespace std::ranges; + int x = inhibited(std::S1{}); // no error +} + +namespace std { + template + S1 inhibited_template(T); + + namespace ranges { + template + void hidden_template(T); + + template + int inhibited_template(T); + } +} + +void test_function_templates() { + hidden_template(std::ranges::S2{}); + // expected-error@-1{{use of undeclared identifier 'hidden_template'; did you mean 'std::ranges::hidden_template'?}} + + using namespace std::ranges; + int x = inhibited_template(std::S1{}); +} + +namespace std { + S1 inhibited_mixed(S1); + + namespace ranges { + template + int inhibited_mixed(T); + } +} + +void test_mixed() { + using namespace std::ranges; + int x = inhibited_mixed(std::S1{}); +} + +// Should be covered by the hidden functions checks, but just to be sure. +void test_ranges_hidden() { + { + std::S1 a = inhibited(std::S1{}); + std::S1 b = inhibited_template(std::S1{}); + std::S1 c = inhibited_mixed(std::S1{}); + } + { + using namespace std; + std::S1 a = inhibited(std::S1{}); + std::S1 b = inhibited_template(std::S1{}); + std::S1 c = inhibited_mixed(std::S1{}); + } +} + +namespace std { + namespace ranges { + void operator-(S2); + + struct hidden_friend_operator { + friend void operator-(hidden_friend_operator i, int) {} + }; + + struct hidden_friend_swap { + friend void swap(hidden_friend_swap, hidden_friend_swap) {} + }; + } +} + +void test_friends_and_operators() { + -std::ranges::S2{}; // no error + std::ranges::hidden_friend_operator{} - 1; // no error + + swap(std::ranges::hidden_friend_swap{}, std::ranges::hidden_friend_swap{}); + // expected-error@-1{{use of undeclared identifier 'swap'}} +}