Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -9090,12 +9090,13 @@ "before C++20">, InGroup, DefaultIgnore; def err_defaulted_comparison_template : Error< "comparison operator template cannot be defaulted">; -def err_defaulted_comparison_out_of_class : Error< - "%sub{select_defaulted_comparison_kind}0 can only be defaulted in a class " - "definition">; def err_defaulted_comparison_param : Error< "invalid parameter type for defaulted %sub{select_defaulted_comparison_kind}0" "; found %1, expected %2%select{| or %4}3">; +def err_defaulted_comparison_function_not_friend : Error< + "defaulted %0 is not a friend of %1">; +def err_defaulted_comparison_function_not_cxx_record : Error< + "argument %1 is not of class type">; def err_defaulted_comparison_param_mismatch : Error< "parameters for defaulted %sub{select_defaulted_comparison_kind}0 " "must have the same type%diff{ (found $ vs $)|}1,2">; Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -8370,9 +8370,70 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, DefaultedComparisonKind DCK) { assert(DCK != DefaultedComparisonKind::None && "not a defaulted comparison"); + CXXRecordDecl *RD = + dyn_cast_or_null(FD->getLexicalDeclContext()); + if (!RD) { + if (auto *MD = dyn_cast_or_null(FD)) { + RD = MD->getParent(); + } + } + auto ParamsHaveSameType = [&] { + if (!Context.hasSameType(FD->getParamDecl(0)->getType(), + FD->getParamDecl(1)->getType())) { + if (!FD->isImplicit()) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_param_mismatch) + << (int)DCK << FD->getParamDecl(0)->getType() + << FD->getParamDecl(0)->getSourceRange() + << FD->getParamDecl(1)->getType() + << FD->getParamDecl(1)->getSourceRange(); + } + return false; + } + return true; + }; + + if (!RD) { + if (!ParamsHaveSameType()) { + return true; + } + auto Param0 = FD->getParamDecl(0) + ->getType() + .getNonReferenceType() + .getUnqualifiedType(); + + RD = Param0->getAsCXXRecordDecl(); + + if (!RD) { + Diag(FD->getLocation(), + diag::err_defaulted_comparison_function_not_cxx_record) + << FD << FD->getParamDecl(0)->getType() + << FD->getParamDecl(0)->getSourceRange(); + return true; + } + + llvm::SmallSet FriendFunctions; + + for (const auto &I : RD->friends()) { + FriendFunctions.insert( + dyn_cast_or_null(I->getFriendDecl())); + } + + bool IsFriend = false; + for (const auto &I : FD->redecls()) { + if (FriendFunctions.count(I)) { + IsFriend = true; + break; + } + } - CXXRecordDecl *RD = dyn_cast(FD->getLexicalDeclContext()); - assert(RD && "defaulted comparison is not defaulted in a class"); + if (!IsFriend) { + Diag(FD->getLocation(), + diag::err_defaulted_comparison_function_not_friend) + << FD << Param0; + + return true; + } + } // Perform any unqualified lookups we're going to need to default this // function. @@ -8411,17 +8472,7 @@ return true; } } - if (FD->getNumParams() == 2 && - !Context.hasSameType(FD->getParamDecl(0)->getType(), - FD->getParamDecl(1)->getType())) { - if (!FD->isImplicit()) { - Diag(FD->getLocation(), diag::err_defaulted_comparison_param_mismatch) - << (int)DCK - << FD->getParamDecl(0)->getType() - << FD->getParamDecl(0)->getSourceRange() - << FD->getParamDecl(1)->getType() - << FD->getParamDecl(1)->getSourceRange(); - } + if (FD->getNumParams() == 2 && !ParamsHaveSameType()) { return true; } @@ -8446,9 +8497,6 @@ MD->setType(Context.getFunctionType(FPT->getReturnType(), FPT->getParamTypes(), EPI)); } - } else { - // A non-member function declared in a class must be a friend. - assert(FD->getFriendObjectKind() && "expected a friend declaration"); } // C++2a [class.eq]p1, [class.rel]p1: @@ -8606,7 +8654,12 @@ { // Build and set up the function body. - CXXRecordDecl *RD = cast(FD->getLexicalParent()); + + CXXRecordDecl *RD = FD->getParamDecl(0) + ->getType() + .getNonReferenceType() + .getUnqualifiedType() + ->getAsCXXRecordDecl(); SourceLocation BodyLoc = FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation(); StmtResult Body = @@ -17078,12 +17131,6 @@ return; } - if (DefKind.isComparison() && - !isa(FD->getLexicalDeclContext())) { - Diag(FD->getLocation(), diag::err_defaulted_comparison_out_of_class) - << (int)DefKind.asComparison(); - return; - } // Issue compatibility warning. We already warned if the operator is // 'operator<=>' when parsing the '<=>' token. @@ -17107,12 +17154,18 @@ FD->setWillHaveBody(false); // If this definition appears within the record, do the checking when - // the record is complete. This is always the case for a defaulted - // comparison. - if (DefKind.isComparison()) + // the record is complete. If it's defined outside the record define + // it now. + if (DefKind.isComparison()) { + if (!isa(FD->getLexicalDeclContext()) && + !CheckExplicitlyDefaultedComparison(getCurScope(), FD, + DefKind.asComparison())) { + + DefineDefaultedFunction(*this, FD, DefaultLoc); + } return; + } auto *MD = cast(FD); - const FunctionDecl *Primary = FD; if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern()) // Ask the template instantiation pattern that actually had the Index: clang/lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- clang/lib/Sema/SemaTemplateInstantiate.cpp +++ clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -753,11 +753,14 @@ << MD->isExplicitlyDefaulted() << DFK.asSpecialMember() << Context.getTagDeclType(MD->getParent()); } else if (DFK.isComparison()) { + auto *RD = FD->getParamDecl(0) + ->getType() + .getNonReferenceType() + .getUnqualifiedType() + ->getAsCXXRecordDecl(); Diags.Report(Active->PointOfInstantiation, diag::note_comparison_synthesized_at) - << (int)DFK.asComparison() - << Context.getTagDeclType( - cast(FD->getLexicalDeclContext())); + << (int)DFK.asComparison() << Context.getTagDeclType(RD); } break; } Index: clang/test/CXX/class/class.compare/class.compare.default/p1.cpp =================================================================== --- clang/test/CXX/class/class.compare/class.compare.default/p1.cpp +++ clang/test/CXX/class/class.compare/class.compare.default/p1.cpp @@ -1,8 +1,6 @@ // RUN: %clang_cc1 -std=c++2a -verify %s struct B {}; -bool operator==(const B&, const B&) = default; // expected-error {{equality comparison operator can only be defaulted in a class definition}} expected-note {{candidate}} -bool operator<=>(const B&, const B&) = default; // expected-error {{three-way comparison operator can only be defaulted in a class definition}} template bool operator<(const B&, const B&) = default; // expected-error {{comparison operator template cannot be defaulted}} @@ -29,11 +27,6 @@ bool operator==(const A&) const = default; // expected-error {{comparison operator template cannot be defaulted}} }; -// FIXME: The wording is not clear as to whether these are valid, but the -// intention is that they are not. -bool operator<(const A&, const A&) = default; // expected-error {{relational comparison operator can only be defaulted in a class definition}} -bool A::operator<(const A&) const = default; // expected-error {{can only be defaulted in a class definition}} - template struct Dependent { using U = typename T::type; bool operator==(U) const = default; // expected-error {{found 'Dependent::U'}} @@ -132,3 +125,98 @@ friend bool operator==(const B&, const B&) = default; // expected-warning {{deleted}} }; } + +namespace P2085 { + +// Test defaulted ref params +struct A { + bool operator==(const A &) const; + friend bool operator!=(const A &, const A &); +}; + +bool A::operator==(const A &) const = default; +bool operator!=(const A &, const A &) = default; + +// Test defaulted value params + +struct B { + friend bool operator==(B, B); + bool operator==(B) const; +}; +bool operator==(B, B) = default; + +bool B::operator==(B) const = default; // expected-error {{invalid parameter type for defaulted equality comparison operator; found 'P2085::B', expected 'const P2085::B &'}} + +// Test for friend of C but not friend of A +struct C { + friend bool operator==(const A &, const A &); +}; +bool operator==(const A &, const A &) = default; // expected-error {{defaulted 'operator==' is not a friend of 'P2085::A'}} + +struct D { + struct E { + bool operator==(const E &) const = default; + friend bool operator!=(const E &, const E &); + }; +}; + +bool operator!=(const D::E &, const D::E &) = default; + +template +struct F { + T __Ignore; + bool operator==(const F &) const = default; + friend bool operator!=(const F &, const F &); +}; + +const bool R0 = F{} == F{}; + +template +bool operator!=(const F &, const F &) = default; // expected-error {{comparison operator template cannot be defaulted}} + +struct G { +}; + +bool operator!=(const G &, const G &) = default; // expected-error {{defaulted 'operator!=' is not a friend of 'P2085::G'}} + +enum foo { a, + b }; + +struct H { + bool operator==(foo) const; +}; + +bool H::operator==(foo) const = default; //expected-error {{invalid parameter type for defaulted equality comparison operator; found 'P2085::foo', expected 'const P2085::H &'}} + +struct K { + friend bool operator==(foo, K); + friend bool operator==(K, foo); +}; + +bool operator==(foo, K) = default; //expected-error {{parameters for defaulted equality comparison operator must have the same type (found 'P2085::foo' vs 'P2085::K')}} +bool operator==(K, foo) = default; //expected-error {{parameters for defaulted equality comparison operator must have the same type (found 'P2085::K' vs 'P2085::foo')}} +bool operator==(foo, foo) = default; //expected-error {{argument 'P2085::foo' is not of class type}} +bool operator==(const foo &, const foo &) = default; //expected-error {{argument 'const P2085::foo &' is not of class type}} + +struct L { + + bool operator==(const A &) const; + int operator<=>(const A &) const; +}; + +bool L::operator==(const A &) const = default; //expected-error {{invalid parameter type for defaulted equality comparison operator; found 'const P2085::A &', expected 'const P2085::L &'}} +int L::operator<=>(const A &) const = default; //expected-error {{invalid parameter type for defaulted three-way comparison operator; found 'const P2085::A &', expected 'const P2085::L &'}} + +template struct Broken { + template friend bool operator==(Broken, Broken) { return T::result; } //expected-error {{type 'void'}} +}; + +struct Q { + Broken<'B'> b; + friend bool operator!=(const Q &, const Q &) = default; //expected-no-error + friend bool operator==(const Q &, const Q &); +}; + +bool operator==(const Q &, const Q &) = default; // expected-note {{in instantiation of}} + +} // namespace P2085 \ No newline at end of file Index: clang/www/cxx_status.html =================================================================== --- clang/www/cxx_status.html +++ clang/www/cxx_status.html @@ -996,7 +996,7 @@ P2085R0 - No + Clang 13 Access checking on specializations