diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -3214,6 +3214,9 @@ /// argument-dependent lookup. bool requiresADL() const { return UnresolvedLookupExprBits.RequiresADL; } + /// A function marked '__disable_adl' inhibits ADL. + void disableADL() { UnresolvedLookupExprBits.RequiresADL = false; } + /// True if this lookup is overloaded. bool isOverloaded() const { return UnresolvedLookupExprBits.Overloaded; } diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -4158,3 +4158,10 @@ let Subjects = SubjectList<[Record]>; let Documentation = [ReadOnlyPlacementDocs]; } + +def DisableADL : InheritableAttr { + let Spellings = [Keyword<"__disable_adl">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [DisableADLDocs]; + let LangOpts = [CPlusPlus]; +} diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6945,8 +6945,74 @@ def WebAssemblyFuncrefDocs : Documentation { let Category = DocCatType; let Content = [{ -Clang supports the ``__funcref`` attribute for the WebAssembly target. -This attribute may be attached to a function pointer type, where it modifies +Clang supports the ``__funcref`` attribute for the WebAssembly target. +This attribute may be attached to a function pointer type, where it modifies its underlying representation to be a WebAssembly ``funcref``. }]; } + +def DisableADLDocs : Documentation { + let Category = DocCatType; + let Content = [{ + This attribute informs the compiler that overloads in the immediate scope should not be found by + argument-dependent lookup (ADL), and that when found by unqualified name lookup, they inhibit + ADL. This is useful for implementing libraries whose design is not centred around ADL, but wish + to continue writing functions as opposed to function objects (which can impact build times). + + Example: + + .. code-block:: cpp + + namespace NS1 { + struct S1 {}; + S1 inhibited(S1); // expected-note 2 {{candidate function}} + + namespace NNS1 { + struct S2 {}; + __disable_adl void hidden(S2); // expected-note{{declared here}} + __disable_adl int inhibited(S1); // expected-note 4 {{candidate function}} + } + } + + namespace NS2 { + __disable_adl void inhibited(NS1::S1); // expected-note 2 {{candidate function}} + } + + int main() + { + NS1::S1 s; + hidden(s); // error: use of undeclared identifier 'hidden'; did you mean 'NS::NNS::hidden'? + { + NS1::S1 x = inhibited(NS1::S1{}); // no error + } + { + using namespace NS1::NNS1; + int x = inhibited(NS1::S1{}); // no error + + using namespace NS1; + S1 y = inhibited(NS1::S1{}); // error: call to 'inhibited' is ambiguous + } + { + using NS1::NNS1::inhibited; + int x = inhibited(NS1::S1{}); // no error + + using NS1::inhibited; + NS1::S1 y = inhibited(NS1::S1{}); // error: call to 'inhibited' is ambiguous + } + { + using namespace NS2; + inhibited(NS1::S1{}); // no error + + using namespace NS1::NNS1; + inhibited(NS1::S1{}); // error: call to 'inhibited' is ambiguous + } + { + using NS2::inhibited; + inhibited(NS1::S1{}); // no error + + using NS1::NNS1::inhibited; + inhibited(NS1::S1{}); // error: call to 'inhibited' is ambiguous + } + } + }]; +} diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3165,6 +3165,9 @@ "vector size not an integral multiple of component size">; def err_attribute_zero_size : Error<"zero %0 size">; def err_attribute_size_too_large : Error<"%0 size too large">; +def err_disable_adl_no_operators : Error< + "can't apply '__disable_adl' to %select{operators, since they're supposed to " + "be used with ADL|member functions}0">; def err_typecheck_sve_ambiguous : Error< "cannot combine fixed-length and sizeless SVE vectors in expression, result is ambiguous (%0 and %1)">; def err_typecheck_sve_rvv_gnu_ambiguous : Error< diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -746,6 +746,7 @@ KEYWORD(__builtin_bit_cast , KEYALL) KEYWORD(__builtin_available , KEYALL) KEYWORD(__builtin_sycl_unique_stable_name, KEYSYCL) +KEYWORD(__disable_adl , KEYCXX) // Clang-specific keywords enabled only in testing. TESTING_KEYWORD(__unknown_anytype , KEYALL) diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -3829,6 +3829,15 @@ ParseAttributes(PAKM_GNU | PAKM_Declspec, DS.getAttributes(), LateAttrs); continue; + case tok::kw___disable_adl: { + IdentifierInfo *AttrName = Tok.getIdentifierInfo(); + SourceLocation AttrNameLoc = Tok.getLocation(); + DS.getAttributes().addNew( + AttrName, AttrNameLoc, /*scopeName=*/nullptr, AttrNameLoc, + /*args=*/nullptr, /*NumArgs=*/0, tok::kw___disable_adl); + break; + } + // Microsoft single token adornments. case tok::kw___forceinline: { isInvalid = DS.setFunctionSpecForceInline(Loc, PrevSpec, DiagID); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -3834,7 +3834,7 @@ S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL; return; } - + if (S.getLangOpts().HLSL) { S.Diag(AL.getLoc(), diag::err_hlsl_init_priority_unsupported); return; @@ -5757,6 +5757,16 @@ D->addAttr(::new (S.Context) BuiltinAliasAttr(S.Context, AL, Ident)); } +static void handleDisableADLAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (FunctionDecl *F = D->getAsFunction(); + F->isOverloadedOperator() || F->isCXXClassMember()) { + S.Diag(AL.getLoc(), diag::err_disable_adl_no_operators) + << F->isCXXClassMember(); + return; + } + D->addAttr(::new (S.Context) DisableADLAttr(S.Context, AL)); +} + //===----------------------------------------------------------------------===// // Checker-specific attribute handlers. //===----------------------------------------------------------------------===// @@ -9363,6 +9373,10 @@ case ParsedAttr::AT_UsingIfExists: handleSimpleAttribute(S, D, AL); break; + + case ParsedAttr::AT_DisableADL: + handleDisableADLAttr(S, D, AL); + break; } } diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -3867,6 +3867,9 @@ !isa(Underlying)) continue; + if (Underlying->getAsFunction()->hasAttr()) + continue; + // The declaration is visible to argument-dependent lookup if either // it's ordinarily visible or declared as a friend in an associated // class. 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 @@ -6528,7 +6528,7 @@ NamedDecl *ND = Function; if (auto *SpecInfo = Function->getTemplateSpecializationInfo()) ND = SpecInfo->getTemplate(); - + if (ND->getFormalLinkage() == Linkage::InternalLinkage) { Candidate.Viable = false; Candidate.FailureKind = ovl_fail_module_mismatched; @@ -12933,18 +12933,20 @@ } /// Add a single candidate to the overload set. -static void AddOverloadedCallCandidate(Sema &S, - DeclAccessPair FoundDecl, - TemplateArgumentListInfo *ExplicitTemplateArgs, - ArrayRef Args, - OverloadCandidateSet &CandidateSet, - bool PartialOverloading, - bool KnownValid) { +static void AddOverloadedCallCandidate( + Sema &S, DeclAccessPair FoundDecl, + TemplateArgumentListInfo *ExplicitTemplateArgs, ArrayRef Args, + OverloadCandidateSet &CandidateSet, bool PartialOverloading, + bool KnownValid, UnresolvedLookupExpr *ULE) { NamedDecl *Callee = FoundDecl.getDecl(); if (isa(Callee)) Callee = cast(Callee)->getTargetDecl(); if (FunctionDecl *Func = dyn_cast(Callee)) { + if (Func->hasAttr() && ULE) { + ULE->disableADL(); + } + if (ExplicitTemplateArgs) { assert(!KnownValid && "Explicit template arguments?"); return; @@ -12961,6 +12963,10 @@ if (FunctionTemplateDecl *FuncTemplate = dyn_cast(Callee)) { + if (FuncTemplate->getAsFunction()->hasAttr() && ULE) { + ULE->disableADL(); + } + S.AddTemplateOverloadCandidate(FuncTemplate, FoundDecl, ExplicitTemplateArgs, Args, CandidateSet, /*SuppressUserConversions=*/false, @@ -13019,7 +13025,7 @@ E = ULE->decls_end(); I != E; ++I) AddOverloadedCallCandidate(*this, I.getPair(), ExplicitTemplateArgs, Args, CandidateSet, PartialOverloading, - /*KnownValid*/ true); + /*KnownValid=*/true, ULE); if (ULE->requiresADL()) AddArgumentDependentLookupCandidates(ULE->getName(), ULE->getExprLoc(), @@ -13034,7 +13040,8 @@ ArrayRef Args, OverloadCandidateSet &CandidateSet) { for (LookupResult::iterator I = R.begin(), E = R.end(); I != E; ++I) AddOverloadedCallCandidate(*this, I.getPair(), ExplicitTemplateArgs, Args, - CandidateSet, false, /*KnownValid*/ false); + CandidateSet, false, /*KnownValid*/ false, + nullptr); } /// Determine whether a declaration with the specified name could be moved into 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,179 @@ +// RUN: %clang_cc1 %s -fsyntax-only -verify -std=c++20 +// RUN: %clang_cc1 %s -fsyntax-only -verify -std=c++2b + +namespace NS1 { + struct S1 {}; + S1 inhibited(S1); // expected-note 2 {{candidate function}} + + namespace NNS1 { + struct S2 {}; + __disable_adl void hidden(S2); // expected-note{{declared here}} + __disable_adl int inhibited(S1); // expected-note 4 {{candidate function}} + } +} + +namespace NS2 { + __disable_adl void inhibited(NS1::S1); // expected-note 2 {{candidate function}} +} + +void test_functions() { + hidden(NS1::NNS1::S2{}); // expected-error{{use of undeclared identifier 'hidden'; did you mean 'NS1::NNS1::hidden'?}} + { + NS1::S1 x = inhibited(NS1::S1{}); // no error + } + { + using namespace NS1::NNS1; + int x = inhibited(NS1::S1{}); // no error + + using namespace NS1; + S1 y = inhibited(NS1::S1{}); // expected-error{{call to 'inhibited' is ambiguous}} + } + { + using NS1::NNS1::inhibited; + int x = inhibited(NS1::S1{}); // no error + + using NS1::inhibited; + NS1::S1 y = inhibited(NS1::S1{}); // expected-error{{call to 'inhibited' is ambiguous}} + } + { + using namespace NS2; + inhibited(NS1::S1{}); // no error + + using namespace NS1::NNS1; + inhibited(NS1::S1{}); // expected-error{{call to 'inhibited' is ambiguous}} + } + { + using NS2::inhibited; + inhibited(NS1::S1{}); // no error + + using NS1::NNS1::inhibited; + inhibited(NS1::S1{}); // expected-error{{call to 'inhibited' is ambiguous}} + } +} + +namespace NS1 { + template + S1 inhibited_template(T); // expected-note 2 {{candidate function}} + + namespace NNS1 { + template + __disable_adl void hidden_template(T); // expected-note{{declared here}} + + template + __disable_adl int inhibited_template(T); // expected-note 4 {{candidate function}} + } +} + +namespace NS2 { + template + __disable_adl int inhibited_template(T); // expected-note 2 {{candidate function}} +} + +void test_function_templates() { + hidden_template(NS1::NNS1::S2{}); // expected-error{{use of undeclared identifier 'hidden_template'; did you mean 'NS1::NNS1::hidden_template'?}} + + { + NS1::S1 x = inhibited_template(NS1::S1{}); // no error + } + { + using namespace NS1::NNS1; + int x = inhibited_template(NS1::S1{}); // no error + + using namespace NS1; + S1 y = inhibited_template(NS1::S1{}); // expected-error{{call to 'inhibited_template' is ambiguous}} + } + { + using NS1::NNS1::inhibited_template; + int x = inhibited_template(NS1::S1{}); // no error + + using NS1::inhibited_template; + NS1::S1 y = inhibited_template(NS1::S1{}); // expected-error{{call to 'inhibited_template' is ambiguous}} + } + { + using namespace NS2; + inhibited_template(NS1::S1{}); // no error + + using namespace NS1::NNS1; + inhibited_template(NS1::S1{}); // expected-error{{call to 'inhibited_template' is ambiguous}} + } + { + using NS2::inhibited_template; + inhibited_template(NS1::S1{}); // no error + + using NS1::NNS1::inhibited_template; + inhibited_template(NS1::S1{}); // expected-error{{call to 'inhibited_template' is ambiguous}} + } +} + +namespace NS1 { + S1 inhibited_mixed(S1); + + namespace NNS1 { + template + __disable_adl int inhibited_mixed(T); + } +} + +void test_mixed() { + using namespace NS1::NNS1; + int x = inhibited_mixed(NS1::S1{}); // no error +} + +// Should be covered by the hidden functions checks, but just to be sure. +void test_NNS1_hidden() { + { + NS1::S1 a = inhibited(NS1::S1{}); + NS1::S1 b = inhibited_template(NS1::S1{}); + NS1::S1 c = inhibited_mixed(NS1::S1{}); + } + { + using namespace NS1; + NS1::S1 a = inhibited(NS1::S1{}); + NS1::S1 b = inhibited_template(NS1::S1{}); + NS1::S1 c = inhibited_mixed(NS1::S1{}); + } +} + +namespace NS1 { + namespace NNS1 { + __disable_adl void operator-(S2); // expected-error{{can't apply '__disable_adl' to operators, since they're supposed to be used with ADL}} + + struct hidden_friend_operator { + friend void operator-(hidden_friend_operator i, int) {} + }; + + struct hidden_friend_swap { + __disable_adl friend void swap(hidden_friend_swap, hidden_friend_swap) {} + }; + } +} + +void test_friends_and_operators() { + -NS1::NNS1::S2{}; // no error + NS1::NNS1::hidden_friend_operator{} - 1; // no error + + swap(NS1::NNS1::hidden_friend_swap{}, NS1::NNS1::hidden_friend_swap{}); // expected-error{{use of undeclared identifier 'swap'}} +} + +struct S { + __disable_adl void f(); // expected-error{{can't apply '__disable_adl' to member functions}} + __disable_adl static void g(); // expected-error{{can't apply '__disable_adl' to member functions}} +}; + +template using common_comparison_category_t = int; +template T declval; +template __disable_adl auto synth_three_way(); +template using synth_three_way_result = decltype(synth_three_way(declval)); +template concept three_way_comparable_synthesisable = requires { synth_three_way; }; +template struct pair { + template + auto operator>(pair) -> common_comparison_category_t>; +}; +struct pair_spaceship_invalid { + pair_spaceship_invalid() { test>(); } // expected-note{{}} + template void test() noexcept; +}; +template void pair_spaceship_invalid::test() noexcept { + auto p1 = T(), p2 = T(); + requires { p1 > p2; }; // expected-warning 2 {{expression result unused}} +}