diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -348,6 +348,8 @@ - Fix issue that the standard C++ modules importer will call global constructor/destructor for the global varaibles in the importing modules. This fixes `Issue 59765 `_ +- Reject in-class defaulting of previosly declared comparison operators. Fixes + `Issue 51227 `_. Improvements to Clang's diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 @@ -9363,6 +9363,9 @@ "defaulting %select{this %sub{select_defaulted_comparison_kind}1|" "the corresponding implicit 'operator==' for this defaulted 'operator<=>'}0 " "would delete it after its first declaration">; +def err_non_first_default_compare_in_class : Error< + "defaulting this %sub{select_defaulted_comparison_kind}0 " + "is not allowed because it was already declared outside the class">; def note_defaulted_comparison_union : Note< "defaulted %0 is implicitly deleted because " "%2 is a %select{union-like class|union}1 with variant members">; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -8734,10 +8734,8 @@ bool First = FD == FD->getCanonicalDecl(); - // If we want to delete the function, then do so; there's nothing else to - // check in that case. - if (Info.Deleted) { - if (!First) { + if (!First) { + if (Info.Deleted) { // C++11 [dcl.fct.def.default]p4: // [For a] user-provided explicitly-defaulted function [...] if such a // function is implicitly defined as deleted, the program is ill-formed. @@ -8751,7 +8749,21 @@ .visit(); return true; } + if (isa(FD->getLexicalDeclContext())) { + // C++20 [class.compare.default]p1: + // [...] A definition of a comparison operator as defaulted that appears + // in a class shall be the first declaration of that function. + Diag(FD->getLocation(), diag::err_non_first_default_compare_in_class) + << (int)DCK; + Diag(FD->getCanonicalDecl()->getLocation(), + diag::note_previous_declaration); + return true; + } + } + // If we want to delete the function, then do so; there's nothing else to + // check in that case. + if (Info.Deleted) { SetDeclDeleted(FD, FD->getLocation()); if (!inTemplateInstantiation() && !FD->isImplicit()) { Diag(FD->getLocation(), diag::warn_defaulted_comparison_deleted) diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p1.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p1.cpp --- a/clang/test/CXX/class/class.compare/class.compare.default/p1.cpp +++ b/clang/test/CXX/class/class.compare/class.compare.default/p1.cpp @@ -179,6 +179,12 @@ struct S5; // expected-note 3{{forward declaration}} bool operator==(S5, S5) = default; // expected-error{{not a friend}} expected-error 2{{has incomplete type}} +struct S6; +bool operator==(const S6&, const S6&); // expected-note {{previous declaration}} +struct S6 { + friend bool operator==(const S6&, const S6&) = default; // expected-error {{because it was already declared outside}} +}; + enum e {}; bool operator==(e, int) = default; // expected-error{{expected class or reference to a constant class}} diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp --- a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp @@ -139,13 +139,13 @@ struct F; bool operator==(const F&, const F&); -bool operator!=(const F&, const F&); +bool operator!=(const F&, const F&); // expected-note {{previous declaration}} bool operator<=>(const F&, const F&); -bool operator<(const F&, const F&); +bool operator<(const F&, const F&); // expected-note {{previous declaration}} struct F { union { int a; }; friend bool operator==(const F&, const F&) = default; // expected-error {{defaulting this equality comparison operator would delete it after its first declaration}} expected-note {{implicitly deleted because 'F' is a union-like class}} - friend bool operator!=(const F&, const F&) = default; + friend bool operator!=(const F&, const F&) = default; // expected-error {{because it was already declared outside}} friend bool operator<=>(const F&, const F&) = default; // expected-error {{defaulting this three-way comparison operator would delete it after its first declaration}} expected-note {{implicitly deleted because 'F' is a union-like class}} - friend bool operator<(const F&, const F&) = default; + friend bool operator<(const F&, const F&) = default; // expected-error {{because it was already declared outside}} }; diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp --- a/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp +++ b/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp @@ -165,22 +165,22 @@ // FIXME: This rule creates problems for reordering of declarations; is this // really the right model? struct G; -bool operator==(const G&, const G&); -bool operator!=(const G&, const G&); -std::strong_ordering operator<=>(const G&, const G&); -bool operator<(const G&, const G&); -bool operator<=(const G&, const G&); -bool operator>(const G&, const G&); -bool operator>=(const G&, const G&); +bool operator==(const G&, const G&); // expected-note {{previous declaration}} +bool operator!=(const G&, const G&); // expected-note {{previous declaration}} +std::strong_ordering operator<=>(const G&, const G&); // expected-note {{previous declaration}} +bool operator<(const G&, const G&); // expected-note {{previous declaration}} +bool operator<=(const G&, const G&); // expected-note {{previous declaration}} +bool operator>(const G&, const G&); // expected-note {{previous declaration}} +bool operator>=(const G&, const G&); // expected-note {{previous declaration}} struct G { - friend bool operator==(const G&, const G&) = default; - friend bool operator!=(const G&, const G&) = default; - - friend std::strong_ordering operator<=>(const G&, const G&) = default; - friend bool operator<(const G&, const G&) = default; - friend bool operator<=(const G&, const G&) = default; - friend bool operator>(const G&, const G&) = default; - friend bool operator>=(const G&, const G&) = default; + friend bool operator==(const G&, const G&) = default; // expected-error {{because it was already declared outside}} + friend bool operator!=(const G&, const G&) = default; // expected-error {{because it was already declared outside}} + + friend std::strong_ordering operator<=>(const G&, const G&) = default; // expected-error {{because it was already declared outside}} + friend bool operator<(const G&, const G&) = default; // expected-error {{because it was already declared outside}} + friend bool operator<=(const G&, const G&) = default; // expected-error {{because it was already declared outside}} + friend bool operator>(const G&, const G&) = default; // expected-error {{because it was already declared outside}} + friend bool operator>=(const G&, const G&) = default; // expected-error {{because it was already declared outside}} }; bool operator==(const G&, const G&); bool operator!=(const G&, const G&);