<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<style type="text/css" style="display:none"><!-- p { margin-top: 0px; margin-bottom: 0px; }--></style>
</head>
<body dir="ltr" style="font-size:12pt;color:#000000;background-color:#FFFFFF;font-family:Calibri,Arial,Helvetica,sans-serif;">
<p></p>
<div dir="ltr">Hi Richard,
<div><br>
</div>
<div><a href="http://lab.llvm.org:8011/builders/llvm-clang-win-x-armv7l/builds/1353" title="http://lab.llvm.org:8011/builders/llvm-clang-win-x-armv7l/builds/1353
Ctrl+Click or tap to follow the link">http://lab.llvm.org:8011/builders/llvm-clang-win-x-armv7l/builds/1353</a><br>
</div>
<div><br>
</div>
<div>The bots are broken for a long time now.</div>
<div>When do you expect the fix?</div>
<div><br>
</div>
<div>Thanks,</div>
<div>Alex<br>
</div>
</div>
<div><br>
</div>
<br>
<div class="gmail_quote">
<div dir="ltr" class="gmail_attr">On Wed, Dec 11, 2019 at 12:13 AM Yvan Roux via cfe-commits <cfe-commits@lists.llvm.org> wrote:<br>
</div>
<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex; border-left:1px solid rgb(204,204,204); padding-left:1ex">
Hi Richard,<br>
<br>
ARM bots are broken after this commit, logs are available here:<br>
<br>
<a href="http://lab.llvm.org:8011/builders/clang-cmake-armv7-quick/builds/12109/steps/ninja%20check%201/logs/FAIL%3A%20Clang%3A%3Acxx2a-three-way-comparison.cpp" rel="noreferrer" target="_blank">http://lab.llvm.org:8011/build<wbr>ers/clang-cmake-armv7-quick/<wbr>builds/12109/steps/ninja%<wbr>20check%201/logs/FAIL%3A%<wbr>20Clang%3A%3Acxx2a-three-way-<wbr>comparison.cpp</a><br>
<br>
Thanks,<br>
Yvan<br>
<br>
On Wed, 11 Dec 2019 at 02:24, Richard Smith via cfe-commits<br>
<<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a>> wrote:<br>
><br>
><br>
> Author: Richard Smith<br>
> Date: 2019-12-10T17:24:27-08:00<br>
> New Revision: bc24014b9765a454cb5214f4871451<wbr>a41ffb7d29<br>
><br>
> URL: <a href="https://github.com/llvm/llvm-project/commit/bc24014b9765a454cb5214f4871451a41ffb7d29" rel="noreferrer" target="_blank">
https://github.com/llvm/llvm-p<wbr>roject/commit/bc24014b9765a454<wbr>cb5214f4871451a41ffb7d29</a><br>
> DIFF: <a href="https://github.com/llvm/llvm-project/commit/bc24014b9765a454cb5214f4871451a41ffb7d29.diff" rel="noreferrer" target="_blank">
https://github.com/llvm/llvm-p<wbr>roject/commit/bc24014b9765a454<wbr>cb5214f4871451a41ffb7d29.diff</a><br>
><br>
> LOG: [c++20] Implement P1185R2 (as modified by P2002R0).<br>
><br>
> For each defaulted operator<=> in a class that doesn't explicitly<br>
> declare any operator==, also inject a matching implicit defaulted<br>
> operator==.<br>
><br>
> Added:<br>
>     clang/test/CXX/class/class.co<wbr>mpare/class.compare.default/p4<wbr>.cpp<br>
><br>
> Modified:<br>
>     clang/include/clang/Basic/Dia<wbr>gnosticSemaKinds.td<br>
>     clang/include/clang/Sema/<wbr>Sema.h<br>
>     clang/include/clang/Sema/Temp<wbr>late.h<br>
>     clang/lib/Frontend/FrontendAc<wbr>tions.cpp<br>
>     clang/lib/Sema/SemaDeclCXX.<wbr>cpp<br>
>     clang/lib/Sema/SemaOverload.c<wbr>pp<br>
>     clang/lib/Sema/SemaTemplateIn<wbr>stantiate.cpp<br>
>     clang/lib/Sema/SemaTemplateIn<wbr>stantiateDecl.cpp<br>
>     clang/test/CodeGenCXX/cxx2a-t<wbr>hree-way-comparison.cpp<br>
><br>
> Removed:<br>
><br>
><br>
><br>
> ##############################<wbr>##############################<wbr>####################<br>
> diff  --git a/clang/include/clang/Basic/Di<wbr>agnosticSemaKinds.td b/clang/include/clang/Basic/Di<wbr>agnosticSemaKinds.td<br>
> index aeeff2b9a76e..a5f35996cfdc 100644<br>
> --- a/clang/include/clang/Basic/Di<wbr>agnosticSemaKinds.td<br>
> +++ b/clang/include/clang/Basic/Di<wbr>agnosticSemaKinds.td<br>
> @@ -3840,6 +3840,7 @@ def select_ovl_candidate_kind : TextSubstitution<<br>
>      "constructor (the implicit move constructor)|"<br>
>      "function (the implicit copy assignment operator)|"<br>
>      "function (the implicit move assignment operator)|"<br>
> +    "function (the implicit 'operator==' for this 'operator<=>)'|"<br>
>      "inherited constructor}0%select{| template| %2}1">;<br>
><br>
>  def note_ovl_candidate : Note<<br>
> @@ -8207,7 +8208,8 @@ def warn_defaulted_comparison_dele<wbr>ted : Warning<<br>
>    "explicitly defaulted %sub{select_defaulted_comparis<wbr>on_kind}0 is implicitly "<br>
>    "deleted">, InGroup<DefaultedFunctionDelet<wbr>ed>;<br>
>  def err_non_first_default_compare_<wbr>deletes : Error<<br>
> -  "defaulting this %sub{select_defaulted_comparis<wbr>on_kind}0 "<br>
> +  "defaulting %select{this %sub{select_defaulted_comparis<wbr>on_kind}1|"<br>
> +  "the corresponding implicit 'operator==' for this defaulted 'operator<=>'}0 "<br>
>    "would delete it after its first declaration">;<br>
>  def note_defaulted_comparison_unio<wbr>n : Note<<br>
>    "defaulted %0 is implicitly deleted because "<br>
> @@ -8237,14 +8239,19 @@ def note_defaulted_comparison_cann<wbr>ot_deduce : Note<<br>
>  def note_defaulted_comparison_cann<wbr>ot_deduce_callee : Note<<br>
>    "selected 'operator<=>' for %select{|member|base class}0 %1 declared here">;<br>
>  def err_incorrect_defaulted_compar<wbr>ison_constexpr : Error<<br>
> -  "defaulted definition of %sub{select_defaulted_comparis<wbr>on_kind}0 "<br>
> -  "cannot be declared %select{constexpr|consteval}1 because it invokes "<br>
> -  "a non-constexpr comparison function">;<br>
> +  "defaulted definition of %select{%sub{select_defaulted_<wbr>comparison_kind}1|"<br>
> +  "three-way comparison operator}0 "<br>
> +  "cannot be declared %select{constexpr|consteval}2 because "<br>
> +  "%select{it|the corresponding implicit 'operator=='}0 "<br>
> +  "invokes a non-constexpr comparison function">;<br>
>  def note_defaulted_comparison_not_<wbr>constexpr : Note<<br>
>    "non-constexpr comparison function would be used to compare "<br>
>    "%select{|member %1|base class %1}0">;<br>
>  def note_defaulted_comparison_not_<wbr>constexpr_here : Note<<br>
>    "non-constexpr comparison function declared here">;<br>
> +def note_in_declaration_of_implici<wbr>t_equality_comparison : Note<<br>
> +  "while declaring the corresponding implicit 'operator==' "<br>
> +  "for this defaulted 'operator<=>'">;<br>
><br>
>  def ext_implicit_exception_spec_mi<wbr>smatch : ExtWarn<<br>
>    "function previously declared with an %select{explicit|implicit}0 exception "<br>
><br>
> diff  --git a/clang/include/clang/Sema/Sem<wbr>a.h b/clang/include/clang/Sema/Sem<wbr>a.h<br>
> index 1cdaab3dc28c..5a1ee507218c 100644<br>
> --- a/clang/include/clang/Sema/Sem<wbr>a.h<br>
> +++ b/clang/include/clang/Sema/Sem<wbr>a.h<br>
> @@ -6524,6 +6524,8 @@ class Sema final {<br>
><br>
>    bool CheckExplicitlyDefaultedCompar<wbr>ison(Scope *S, FunctionDecl *MD,<br>
>                                            DefaultedComparisonKind DCK);<br>
> +  void DeclareImplicitEqualityCompari<wbr>son(CXXRecordDecl *RD,<br>
> +                                         FunctionDecl *Spaceship);<br>
>    void DefineDefaultedComparison(Sour<wbr>ceLocation Loc, FunctionDecl *FD,<br>
>                                   DefaultedComparisonKind DCK);<br>
><br>
> @@ -7838,6 +7840,10 @@ class Sema final {<br>
>        /// We are declaring an implicit special member function.<br>
>        DeclaringSpecialMember,<br>
><br>
> +      /// We are declaring an implicit 'operator==' for a defaulted<br>
> +      /// 'operator<=>'.<br>
> +      DeclaringImplicitEqualityCompa<wbr>rison,<br>
> +<br>
>        /// We are defining a synthesized function (such as a defaulted special<br>
>        /// member).<br>
>        DefiningSynthesizedFunction,<br>
> @@ -8468,6 +8474,11 @@ class Sema final {<br>
>    Decl *SubstDecl(Decl *D, DeclContext *Owner,<br>
>                    const MultiLevelTemplateArgumentList &TemplateArgs);<br>
><br>
> +  /// Substitute the name and return type of a defaulted 'operator<=>' to form<br>
> +  /// an implicit 'operator=='.<br>
> +  FunctionDecl *SubstSpaceshipAsEqualEqual(CX<wbr>XRecordDecl *RD,<br>
> +                                           FunctionDecl *Spaceship);<br>
> +<br>
>    ExprResult SubstInitializer(Expr *E,<br>
>                         const MultiLevelTemplateArgumentList &TemplateArgs,<br>
>                         bool CXXDirectInit);<br>
><br>
> diff  --git a/clang/include/clang/Sema/Tem<wbr>plate.h b/clang/include/clang/Sema/Tem<wbr>plate.h<br>
> index 102e525e2e16..4c1cfecd4de6 100644<br>
> --- a/clang/include/clang/Sema/Tem<wbr>plate.h<br>
> +++ b/clang/include/clang/Sema/Tem<wbr>plate.h<br>
> @@ -473,13 +473,21 @@ class VarDecl;<br>
><br>
>  #include "clang/AST/DeclNodes.inc"<br>
><br>
> +    enum class RewriteKind { None, RewriteSpaceshipAsEqualEqual };<br>
> +<br>
> +    void adjustForRewrite(RewriteKind RK, FunctionDecl *Orig, QualType &T,<br>
> +                          TypeSourceInfo *&TInfo,<br>
> +                          DeclarationNameInfo &NameInfo);<br>
> +<br>
>      // A few supplemental visitor functions.<br>
>      Decl *VisitCXXMethodDecl(CXXMethodD<wbr>ecl *D,<br>
>                               TemplateParameterList *TemplateParams,<br>
>                               Optional<const ASTTemplateArgumentListInfo *><br>
> -                                 ClassScopeSpecializationArgs = llvm::None);<br>
> +                                 ClassScopeSpecializationArgs = llvm::None,<br>
> +                             RewriteKind RK = RewriteKind::None);<br>
>      Decl *VisitFunctionDecl(FunctionDec<wbr>l *D,<br>
> -                            TemplateParameterList *TemplateParams);<br>
> +                            TemplateParameterList *TemplateParams,<br>
> +                            RewriteKind RK = RewriteKind::None);<br>
>      Decl *VisitDecl(Decl *D);<br>
>      Decl *VisitVarDecl(VarDecl *D, bool InstantiatingVarTemplate,<br>
>                         ArrayRef<BindingDecl *> *Bindings = nullptr);<br>
><br>
> diff  --git a/clang/lib/Frontend/FrontendA<wbr>ctions.cpp b/clang/lib/Frontend/FrontendA<wbr>ctions.cpp<br>
> index 4d47c768ad3c..aeea63ca323f 100644<br>
> --- a/clang/lib/Frontend/FrontendA<wbr>ctions.cpp<br>
> +++ b/clang/lib/Frontend/FrontendA<wbr>ctions.cpp<br>
> @@ -413,6 +413,8 @@ class DefaultTemplateInstCallback : public TemplateInstantiationCallback {<br>
>        return "ExceptionSpecInstantiation";<br>
>      case CodeSynthesisContext::Declarin<wbr>gSpecialMember:<br>
>        return "DeclaringSpecialMember";<br>
> +    case CodeSynthesisContext::Declarin<wbr>gImplicitEqualityComparison:<br>
> +      return "DeclaringImplicitEqualityComp<wbr>arison";<br>
>      case CodeSynthesisContext::Defining<wbr>SynthesizedFunction:<br>
>        return "DefiningSynthesizedFunction";<br>
>      case CodeSynthesisContext::Rewritin<wbr>gOperatorAsSpaceship:<br>
><br>
> diff  --git a/clang/lib/Sema/SemaDeclCXX.c<wbr>pp b/clang/lib/Sema/SemaDeclCXX.c<wbr>pp<br>
> index 7bbd0114b1e2..72ac81776aae 100644<br>
> --- a/clang/lib/Sema/SemaDeclCXX.c<wbr>pp<br>
> +++ b/clang/lib/Sema/SemaDeclCXX.c<wbr>pp<br>
> @@ -6481,40 +6481,38 @@ void Sema::CheckCompletedCXXClass(S<wbr>cope *S, CXXRecordDecl *Record) {<br>
><br>
>    bool HasMethodWithOverrideControl = false,<br>
>         <wbr>HasOverridingMethodWithoutOver<wbr>rideControl = false;<br>
> -  for (auto *M : Record->methods()) {<br>
> -    // FIXME: We could do this check for dependent types with non-dependent<br>
> -    // bases.<br>
> -    if (!Record->isDependentType()) {<br>
> -      // See if a method overloads virtual methods in a base<br>
> -      // class without overriding any.<br>
> -      if (!M->isStatic())<br>
> -        DiagnoseHiddenVirtualMethods(M<wbr>);<br>
> -      if (M->hasAttr<OverrideAttr>())<br>
> -        HasMethodWithOverrideControl = true;<br>
> -      else if (M->size_overridden_methods() > 0)<br>
> -        HasOverridingMethodWithoutOver<wbr>rideControl = true;<br>
> -    }<br>
> +  for (auto *D : Record->decls()) {<br>
> +    if (auto *M = dyn_cast<CXXMethodDecl>(D)) {<br>
> +      // FIXME: We could do this check for dependent types with non-dependent<br>
> +      // bases.<br>
> +      if (!Record->isDependentType()) {<br>
> +        // See if a method overloads virtual methods in a base<br>
> +        // class without overriding any.<br>
> +        if (!M->isStatic())<br>
> +          DiagnoseHiddenVirtualMethods(M<wbr>);<br>
> +        if (M->hasAttr<OverrideAttr>())<br>
> +          HasMethodWithOverrideControl = true;<br>
> +        else if (M->size_overridden_methods() > 0)<br>
> +          HasOverridingMethodWithoutOver<wbr>rideControl = true;<br>
> +      }<br>
><br>
> -    if (!isa<CXXDestructorDecl>(M))<br>
> -      CompleteMemberFunction(M);<br>
> +      if (!isa<CXXDestructorDecl>(M))<br>
> +        CompleteMemberFunction(M);<br>
> +    } else if (auto *F = dyn_cast<FriendDecl>(D)) {<br>
> +      CheckForDefaultedFunction(<br>
> +          dyn_cast_or_null<FunctionDecl><wbr>(F->getFriendDecl()));<br>
> +    }<br>
>    }<br>
><br>
>    if (HasMethodWithOverrideControl &&<br>
>        HasOverridingMethodWithoutOver<wbr>rideControl) {<br>
>      // At least one method has the 'override' control declared.<br>
> -    // Diagnose all other overridden methods which do not have 'override' specified on them.<br>
> +    // Diagnose all other overridden methods which do not have 'override'<br>
> +    // specified on them.<br>
>      for (auto *M : Record->methods())<br>
>        DiagnoseAbsenceOfOverrideContr<wbr>ol(M);<br>
>    }<br>
><br>
> -  // Process any defaulted friends in the member-specification.<br>
> -  if (!Record->isDependentType()) {<br>
> -    for (FriendDecl *D : Record->friends()) {<br>
> -      CheckForDefaultedFunction(<br>
> -          dyn_cast_or_null<FunctionDecl><wbr>(D->getFriendDecl()));<br>
> -    }<br>
> -  }<br>
> -<br>
>    // Check the defaulted secondary comparisons after any other member functions.<br>
>    for (FunctionDecl *FD : DefaultedSecondaryComparisons)<br>
>      CheckExplicitlyDefaultedFuncti<wbr>on(S, FD);<br>
> @@ -7868,7 +7866,9 @@ static void lookupOperatorsForDefaultedCom<wbr>parison(Sema &Self, Scope *S,<br>
>      Lookup(ExtraOp);<br>
><br>
>    // For 'operator<=>', we also form a 'cmp != 0' expression, and might<br>
> -  // synthesize a three-way comparison from '<' and '=='.<br>
> +  // synthesize a three-way comparison from '<' and '=='. In a dependent<br>
> +  // context, we also need to look up '==' in case we implicitly declare a<br>
> +  // defaulted 'operator=='.<br>
>    if (Op == OO_Spaceship) {<br>
>      Lookup(OO_ExclaimEqual);<br>
>      Lookup(OO_Less);<br>
> @@ -7904,9 +7904,13 @@ bool Sema::CheckExplicitlyDefaulted<wbr>Comparison(Scope *S, FunctionDecl *FD,<br>
>    for (const ParmVarDecl *Param : FD->parameters()) {<br>
>      if (!Param->getType()->isDependen<wbr>tType() &&<br>
>          !Context.hasSameType(Param->ge<wbr>tType(), ExpectedParmType)) {<br>
> -      Diag(FD->getLocation(), diag::err_defaulted_comparison<wbr>_param)<br>
> -          << (int)DCK << Param->getType() << ExpectedParmType<br>
> -          << Param->getSourceRange();<br>
> +      // Don't diagnose an implicit 'operator=='; we will have diagnosed the<br>
> +      // corresponding defaulted 'operator<=>' already.<br>
> +      if (!FD->isImplicit()) {<br>
> +        Diag(FD->getLocation(), diag::err_defaulted_comparison<wbr>_param)<br>
> +            << (int)DCK << Param->getType() << ExpectedParmType<br>
> +            << Param->getSourceRange();<br>
> +      }<br>
>        return true;<br>
>      }<br>
>    }<br>
> @@ -7918,8 +7922,12 @@ bool Sema::CheckExplicitlyDefaulted<wbr>Comparison(Scope *S, FunctionDecl *FD,<br>
>        SourceLocation InsertLoc;<br>
>        if (FunctionTypeLoc Loc = MD->getFunctionTypeLoc())<br>
>          InsertLoc = getLocForEndOfToken(Loc.getRPa<wbr>renLoc());<br>
> -      Diag(MD->getLocation(), diag::err_defaulted_comparison<wbr>_non_const)<br>
> -        << (int)DCK << FixItHint::CreateInsertion(Ins<wbr>ertLoc, " const");<br>
> +      // Don't diagnose an implicit 'operator=='; we will have diagnosed the<br>
> +      // corresponding defaulted 'operator<=>' already.<br>
> +      if (!MD->isImplicit()) {<br>
> +        Diag(MD->getLocation(), diag::err_defaulted_comparison<wbr>_non_const)<br>
> +          << (int)DCK << FixItHint::CreateInsertion(Ins<wbr>ertLoc, " const");<br>
> +      }<br>
><br>
>        // Add the 'const' to the type to recover.<br>
>        const auto *FPT = MD->getType()->castAs<Function<wbr>ProtoType>();<br>
> @@ -7980,7 +7988,7 @@ bool Sema::CheckExplicitlyDefaulted<wbr>Comparison(Scope *S, FunctionDecl *FD,<br>
>        // This is really just a consequence of the general rule that you can<br>
>        // only delete a function on its first declaration.<br>
>        Diag(FD->getLocation(), diag::err_non_first_default_co<wbr>mpare_deletes)<br>
> -          << (int)DCK;<br>
> +          << FD->isImplicit() << (int)DCK;<br>
>        DefaultedComparisonAnalyzer(*t<wbr>his, RD, FD, DCK,<br>
>                                    DefaultedComparisonAnalyzer::E<wbr>xplainDeleted)<br>
>            .visit();<br>
> @@ -7988,7 +7996,7 @@ bool Sema::CheckExplicitlyDefaulted<wbr>Comparison(Scope *S, FunctionDecl *FD,<br>
>      }<br>
><br>
>      SetDeclDeleted(FD, FD->getLocation());<br>
> -    if (!inTemplateInstantiation()) {<br>
> +    if (!inTemplateInstantiation() && !FD->isImplicit()) {<br>
>        Diag(FD->getLocation(), diag::warn_defaulted_compariso<wbr>n_deleted)<br>
>            << (int)DCK;<br>
>        DefaultedComparisonAnalyzer(*t<wbr>his, RD, FD, DCK,<br>
> @@ -8028,7 +8036,7 @@ bool Sema::CheckExplicitlyDefaulted<wbr>Comparison(Scope *S, FunctionDecl *FD,<br>
>          !Info.Constexpr) {<br>
>        Diag(FD->getBeginLoc(),<br>
>             diag::err_incorrect_<wbr>defaulted_comparison_<wbr>constexpr)<br>
> -          << (int)DCK << FD->isConsteval();<br>
> +          << FD->isImplicit() << (int)DCK << FD->isConsteval();<br>
>        DefaultedComparisonAnalyzer(*t<wbr>his, RD, FD, DCK,<br>
>                                    DefaultedComparisonAnalyzer::E<wbr>xplainConstexpr)<br>
>            .visit();<br>
> @@ -8051,6 +8059,20 @@ bool Sema::CheckExplicitlyDefaulted<wbr>Comparison(Scope *S, FunctionDecl *FD,<br>
>    return false;<br>
>  }<br>
><br>
> +void Sema::DeclareImplicitEqualityC<wbr>omparison(CXXRecordDecl *RD,<br>
> +                                             FunctionDecl *Spaceship) {<br>
> +  Sema::CodeSynthesisContext Ctx;<br>
> +  Ctx.Kind = Sema::CodeSynthesisContext::De<wbr>claringImplicitEqualityCompari<wbr>son;<br>
> +  Ctx.PointOfInstantiation = Spaceship->getEndLoc();<br>
> +  Ctx.Entity = Spaceship;<br>
> +  pushCodeSynthesisContext(Ctx);<br>
> +<br>
> +  if (FunctionDecl *EqualEqual = SubstSpaceshipAsEqualEqual(RD, Spaceship))<br>
> +    EqualEqual->setImplicit();<br>
> +<br>
> +  popCodeSynthesisContext();<br>
> +}<br>
> +<br>
>  void Sema::DefineDefaultedCompariso<wbr>n(SourceLocation UseLoc, FunctionDecl *FD,<br>
>                                       DefaultedComparisonKind DCK) {<br>
>    assert(FD->isDefaulted() && !FD->isDeleted() &&<br>
> @@ -9330,6 +9352,44 @@ void Sema::ActOnFinishCXXMemberSpec<wbr>ification(<br>
>    CheckCompletedCXXClass(S, cast<CXXRecordDecl>(TagDecl));<br>
>  }<br>
><br>
> +/// Find the equality comparison functions that should be implicitly declared<br>
> +/// in a given class definition, per C++2a [class.compare.default]p3.<br>
> +static void findImplicitlyDeclaredEquality<wbr>Comparisons(<br>
> +    ASTContext &Ctx, CXXRecordDecl *RD,<br>
> +    llvm::SmallVectorImpl<Function<wbr>Decl *> &Spaceships) {<br>
> +  DeclarationName EqEq = Ctx.DeclarationNames.getCXXOpe<wbr>ratorName(OO_EqualEqual);<br>
> +  if (!RD->lookup(EqEq).empty())<br>
> +    // Member operator== explicitly declared: no implicit operator==s.<br>
> +    return;<br>
> +<br>
> +  // Traverse friends looking for an '==' or a '<=>'.<br>
> +  for (FriendDecl *Friend : RD->friends()) {<br>
> +    FunctionDecl *FD = dyn_cast_or_null<FunctionDecl><wbr>(Friend->getFriendDecl());<br>
> +    if (!FD) continue;<br>
> +<br>
> +    if (FD->getOverloadedOperator() == OO_EqualEqual) {<br>
> +      // Friend operator== explicitly declared: no implicit operator==s.<br>
> +      Spaceships.clear();<br>
> +      return;<br>
> +    }<br>
> +<br>
> +    if (FD->getOverloadedOperator() == OO_Spaceship &&<br>
> +        FD->isExplicitlyDefaulted())<br>
> +      Spaceships.push_back(FD);<br>
> +  }<br>
> +<br>
> +  // Look for members named 'operator<=>'.<br>
> +  DeclarationName Cmp = Ctx.DeclarationNames.getCXXOpe<wbr>ratorName(OO_Spaceship);<br>
> +  for (NamedDecl *ND : RD->lookup(Cmp)) {<br>
> +    // Note that we could find a non-function here (either a function template<br>
> +    // or a using-declaration). Neither case results in an implicit<br>
> +    // 'operator=='.<br>
> +    if (auto *FD = dyn_cast<FunctionDecl>(ND))<br>
> +      if (FD->isExplicitlyDefaulted())<br>
> +        Spaceships.push_back(FD);<br>
> +  }<br>
> +}<br>
> +<br>
>  /// AddImplicitlyDeclaredMembersTo<wbr>Class - Adds any implicitly-declared<br>
>  /// special functions, such as the default constructor, copy<br>
>  /// constructor, or destructor, to the given C++ class (C++<br>
> @@ -9407,6 +9467,20 @@ void Sema::AddImplicitlyDeclaredMem<wbr>bersToClass(CXXRecordDecl *ClassDecl) {<br>
>          ClassDecl->needsOverloadResolu<wbr>tionForDestructor())<br>
>        DeclareImplicitDestructor(Clas<wbr>sDecl);<br>
>    }<br>
> +<br>
> +  // C++2a [class.compare.default]p3:<br>
> +  //   If the member-specification does not explicitly declare any member or<br>
> +  //   friend named operator==, an == operator function is declared implicitly<br>
> +  //   for each defaulted three-way comparison operator function defined in the<br>
> +  //   member-specification<br>
> +  // FIXME: Consider doing this lazily.<br>
> +  if (getLangOpts().CPlusPlus2a) {<br>
> +    llvm::SmallVector<FunctionDecl<wbr>*, 4> DefaultedSpaceships;<br>
> +    findImplicitlyDeclaredEquality<wbr>Comparisons(Context, ClassDecl,<br>
> +                                              DefaultedSpaceships);<br>
> +    for (auto *FD : DefaultedSpaceships)<br>
> +      DeclareImplicitEqualityCompari<wbr>son(ClassDecl, FD);<br>
> +  }<br>
>  }<br>
><br>
>  unsigned Sema::ActOnReenterTemplateScop<wbr>e(Scope *S, Decl *D) {<br>
><br>
> diff  --git a/clang/lib/Sema/SemaOverload.<wbr>cpp b/clang/lib/Sema/SemaOverload.<wbr>cpp<br>
> index 344e54b7f3fc..5b1394d7b34f 100644<br>
> --- a/clang/lib/Sema/SemaOverload.<wbr>cpp<br>
> +++ b/clang/lib/Sema/SemaOverload.<wbr>cpp<br>
> @@ -9723,6 +9723,7 @@ enum OverloadCandidateKind {<br>
>    oc_implicit_move_constructor,<br>
>    oc_implicit_copy_assignment,<br>
>    oc_implicit_move_assignment,<br>
> +  oc_implicit_equality_compariso<wbr>n,<br>
>    oc_inherited_constructor<br>
>  };<br>
><br>
> @@ -9751,6 +9752,9 @@ ClassifyOverloadCandidate(Sema &S, NamedDecl *Found, FunctionDecl *Fn,<br>
>    }();<br>
><br>
>    OverloadCandidateKind Kind = [&]() {<br>
> +    if (Fn->isImplicit() && Fn->getOverloadedOperator() == OO_EqualEqual)<br>
> +      return oc_implicit_equality_compariso<wbr>n;<br>
> +<br>
>      if (CRK & CRK_Reversed)<br>
>        return oc_reversed_binary_operator;<br>
><br>
><br>
> diff  --git a/clang/lib/Sema/SemaTemplateI<wbr>nstantiate.cpp b/clang/lib/Sema/SemaTemplateI<wbr>nstantiate.cpp<br>
> index 1451fe4bb4d1..6db8eb3e5ed6 100644<br>
> --- a/clang/lib/Sema/SemaTemplateI<wbr>nstantiate.cpp<br>
> +++ b/clang/lib/Sema/SemaTemplateI<wbr>nstantiate.cpp<br>
> @@ -203,6 +203,7 @@ bool Sema::CodeSynthesisContext::is<wbr>InstantiationRecord() const {<br>
><br>
>    case DefaultTemplateArgumentCheckin<wbr>g:<br>
>    case DeclaringSpecialMember:<br>
> +  case DeclaringImplicitEqualityCompa<wbr>rison:<br>
>    case DefiningSynthesizedFunction:<br>
>    case ExceptionSpecEvaluation:<br>
>    case ConstraintSubstitution:<br>
> @@ -671,6 +672,11 @@ void Sema::PrintInstantiationStack(<wbr>) {<br>
>          << cast<CXXRecordDecl>(Active->En<wbr>tity) << Active->SpecialMember;<br>
>        break;<br>
><br>
> +    case CodeSynthesisContext::Declarin<wbr>gImplicitEqualityComparison:<br>
> +      Diags.Report(Active->Entity->g<wbr>etLocation(),<br>
> +                   diag::note_in_declaration_of_<wbr>implicit_equality_comparison);<br>
> +      break;<br>
> +<br>
>      case CodeSynthesisContext::Defining<wbr>SynthesizedFunction: {<br>
>        // FIXME: For synthesized functions that are not defaulted,<br>
>        // produce a note.<br>
> @@ -772,6 +778,7 @@ Optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {<br>
>        return Active->DeductionInfo;<br>
><br>
>      case CodeSynthesisContext::Declarin<wbr>gSpecialMember:<br>
> +    case CodeSynthesisContext::Declarin<wbr>gImplicitEqualityComparison:<br>
>      case CodeSynthesisContext::Defining<wbr>SynthesizedFunction:<br>
>      case CodeSynthesisContext::Rewritin<wbr>gOperatorAsSpaceship:<br>
>        // This happens in a context unrelated to template instantiation, so<br>
><br>
> diff  --git a/clang/lib/Sema/SemaTemplateI<wbr>nstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateI<wbr>nstantiateDecl.cpp<br>
> index 71399ff35908..0bff0747df34 100644<br>
> --- a/clang/lib/Sema/SemaTemplateI<wbr>nstantiateDecl.cpp<br>
> +++ b/clang/lib/Sema/SemaTemplateI<wbr>nstantiateDecl.cpp<br>
> @@ -1794,8 +1794,9 @@ static QualType adjustFunctionTypeForInstantia<wbr>tion(ASTContext &Context,<br>
>  ///   1) instantiating function templates<br>
>  ///   2) substituting friend declarations<br>
>  ///   3) substituting deduction guide declarations for nested class templates<br>
> -Decl *TemplateDeclInstantiator::Vis<wbr>itFunctionDecl(FunctionDecl *D,<br>
> -                                       TemplateParameterList *TemplateParams) {<br>
> +Decl *TemplateDeclInstantiator::Vis<wbr>itFunctionDecl(<br>
> +    FunctionDecl *D, TemplateParameterList *TemplateParams,<br>
> +    RewriteKind FunctionRewriteKind) {<br>
>    // Check whether there is already a function template specialization for<br>
>    // this declaration.<br>
>    FunctionTemplateDecl *FunctionTemplate = D->getDescribedFunctionTemplat<wbr>e();<br>
> @@ -1865,6 +1866,9 @@ Decl *TemplateDeclInstantiator::Vis<wbr>itFunctionDecl(FunctionDecl *D,<br>
>    DeclarationNameInfo NameInfo<br>
>      = SemaRef.SubstDeclarationNameIn<wbr>fo(D->getNameInfo(), TemplateArgs);<br>
><br>
> +  if (FunctionRewriteKind != RewriteKind::None)<br>
> +    adjustForRewrite(FunctionRewri<wbr>teKind, D, T, TInfo, NameInfo);<br>
> +<br>
>    FunctionDecl *Function;<br>
>    if (auto *DGuide = dyn_cast<CXXDeductionGuideDecl<wbr>>(D)) {<br>
>      Function = CXXDeductionGuideDecl::Create(<br>
> @@ -2101,8 +2105,8 @@ Decl *TemplateDeclInstantiator::Vis<wbr>itFunctionDecl(FunctionDecl *D,<br>
><br>
>  Decl *TemplateDeclInstantiator::Vis<wbr>itCXXMethodDecl(<br>
>      CXXMethodDecl *D, TemplateParameterList *TemplateParams,<br>
> -    Optional<const ASTTemplateArgumentListInfo *><br>
> -        ClassScopeSpecializationArgs) {<br>
> +    Optional<const ASTTemplateArgumentListInfo *> ClassScopeSpecializationArgs,<br>
> +    RewriteKind FunctionRewriteKind) {<br>
>    FunctionTemplateDecl *FunctionTemplate = D->getDescribedFunctionTemplat<wbr>e();<br>
>    if (FunctionTemplate && !TemplateParams) {<br>
>      // We are creating a function template specialization from a function<br>
> @@ -2181,13 +2185,17 @@ Decl *TemplateDeclInstantiator::Vis<wbr>itCXXMethodDecl(<br>
>      if (!DC) return nullptr;<br>
>    }<br>
><br>
> +  DeclarationNameInfo NameInfo<br>
> +    = SemaRef.SubstDeclarationNameIn<wbr>fo(D->getNameInfo(), TemplateArgs);<br>
> +<br>
> +  if (FunctionRewriteKind != RewriteKind::None)<br>
> +    adjustForRewrite(FunctionRewri<wbr>teKind, D, T, TInfo, NameInfo);<br>
> +<br>
>    // Build the instantiated method declaration.<br>
>    CXXRecordDecl *Record = cast<CXXRecordDecl>(DC);<br>
>    CXXMethodDecl *Method = nullptr;<br>
><br>
>    SourceLocation StartLoc = D->getInnerLocStart();<br>
> -  DeclarationNameInfo NameInfo<br>
> -    = SemaRef.SubstDeclarationNameIn<wbr>fo(D->getNameInfo(), TemplateArgs);<br>
>    if (CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(D<wbr>)) {<br>
>      Method = CXXConstructorDecl::Create(<br>
>          SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo,<br>
> @@ -3523,6 +3531,73 @@ Decl *Sema::SubstDecl(Decl *D, DeclContext *Owner,<br>
>    return SubstD;<br>
>  }<br>
><br>
> +void TemplateDeclInstantiator::adju<wbr>stForRewrite(RewriteKind RK,<br>
> +                                                FunctionDecl *Orig, QualType &T,<br>
> +                                                TypeSourceInfo *&TInfo,<br>
> +                                                DeclarationNameInfo &NameInfo) {<br>
> +  assert(RK == RewriteKind::RewriteSpaceshipA<wbr>sEqualEqual);<br>
> +<br>
> +  // C++2a [class.compare.default]p3:<br>
> +  //   the return type is replaced with bool<br>
> +  auto *FPT = T->castAs<FunctionProtoType>()<wbr>;<br>
> +  T = SemaRef.Context.getFunctionTyp<wbr>e(<br>
> +      SemaRef.Context.BoolTy, FPT->getParamTypes(), FPT->getExtProtoInfo());<br>
> +<br>
> +  // Update the return type in the source info too. The most straightforward<br>
> +  // way is to create new TypeSourceInfo for the new type. Use the location of<br>
> +  // the '= default' as the location of the new type.<br>
> +  //<br>
> +  // FIXME: Set the correct return type when we initially transform the type,<br>
> +  // rather than delaying it to now.<br>
> +  TypeSourceInfo *NewTInfo =<br>
> +      SemaRef.Context.getTrivialType<wbr>SourceInfo(T, Orig->getEndLoc());<br>
> +  auto OldLoc = TInfo->getTypeLoc().getAsAdjus<wbr>ted<FunctionProtoTypeLoc>();<br>
> +  assert(OldLoc && "type of function is not a function type?");<br>
> +  auto NewLoc = NewTInfo->getTypeLoc().castAs<<wbr>FunctionProtoTypeLoc>();<br>
> +  for (unsigned I = 0, N = OldLoc.getNumParams(); I != N; ++I)<br>
> +    NewLoc.setParam(I, OldLoc.getParam(I));<br>
> +  TInfo = NewTInfo;<br>
> +<br>
> +  //   and the declarator-id is replaced with operator==<br>
> +  NameInfo.setName(<br>
> +      SemaRef.Context.DeclarationNam<wbr>es.getCXXOperatorName(OO_Equal<wbr>Equal));<br>
> +}<br>
> +<br>
> +FunctionDecl *Sema::SubstSpaceshipAsEqualEq<wbr>ual(CXXRecordDecl *RD,<br>
> +                                               FunctionDecl *Spaceship) {<br>
> +  if (Spaceship->isInvalidDecl())<br>
> +    return nullptr;<br>
> +<br>
> +  // C++2a [class.compare.default]p3:<br>
> +  //   an == operator function is declared implicitly [...] with the same<br>
> +  //   access and function-definition and in the same class scope as the<br>
> +  //   three-way comparison operator function<br>
> +  MultiLevelTemplateArgumentList NoTemplateArgs;<br>
> +  TemplateDeclInstantiator Instantiator(*this, RD, NoTemplateArgs);<br>
> +  Decl *R;<br>
> +  if (auto *MD = dyn_cast<CXXMethodDecl>(Spaces<wbr>hip)) {<br>
> +    R = Instantiator.VisitCXXMethodDec<wbr>l(<br>
> +        MD, nullptr, None,<br>
> +        TemplateDeclInstantiator::Rewr<wbr>iteKind::RewriteSpaceshipAsEqu<wbr>alEqual);<br>
> +  } else {<br>
> +    assert(Spaceship->getFriendObj<wbr>ectKind() &&<br>
> +           "defaulted spaceship is neither a member nor a friend");<br>
> +<br>
> +    R = Instantiator.VisitFunctionDecl<wbr>(<br>
> +        Spaceship, nullptr,<br>
> +        TemplateDeclInstantiator::Rewr<wbr>iteKind::RewriteSpaceshipAsEqu<wbr>alEqual);<br>
> +    if (!R)<br>
> +      return nullptr;<br>
> +<br>
> +    FriendDecl *FD =<br>
> +        FriendDecl::Create(Context, RD, Spaceship->getLocation(),<br>
> +                           cast<NamedDecl>(R), Spaceship->getBeginLoc());<br>
> +    FD->setAccess(AS_public);<br>
> +    RD->addDecl(FD);<br>
> +  }<br>
> +  return cast_or_null<FunctionDecl>(R);<br>
> +}<br>
> +<br>
>  /// Instantiates a nested template parameter list in the current<br>
>  /// instantiation context.<br>
>  ///<br>
><br>
> diff  --git a/clang/test/CXX/class/class.c<wbr>ompare/class.compare.default/p<wbr>4.cpp b/clang/test/CXX/class/class.c<wbr>ompare/class.compare.default/p<wbr>4.cpp<br>
> new file mode 100644<br>
> index 000000000000..1ab77075277f<br>
> --- /dev/null<br>
> +++ b/clang/test/CXX/class/class.c<wbr>ompare/class.compare.default/p<wbr>4.cpp<br>
> @@ -0,0 +1,146 @@<br>
> +// RUN: %clang_cc1 -std=c++2a -verify %s<br>
> +<br>
> +// This test is for [class.compare.default]p3 as modified and renumbered to p4<br>
> +// by P2002R0.<br>
> +<br>
> +namespace std {<br>
> +  struct strong_ordering {<br>
> +    int n;<br>
> +    constexpr operator int() const { return n; }<br>
> +    static const strong_ordering less, equal, greater;<br>
> +  };<br>
> +  constexpr strong_ordering strong_ordering::less = {-1};<br>
> +  constexpr strong_ordering strong_ordering::equal = {0};<br>
> +  constexpr strong_ordering strong_ordering::greater = {1};<br>
> +}<br>
> +<br>
> +namespace N {<br>
> +  struct A {<br>
> +    friend constexpr std::strong_ordering operator<=>(const A&, const A&) = default;<br>
> +  };<br>
> +<br>
> +  constexpr bool (*test_a_not_found)(const A&, const A&) = &operator==; // expected-error {{undeclared}}<br>
> +<br>
> +  constexpr bool operator==(const A&, const A&);<br>
> +  constexpr bool (*test_a)(const A&, const A&) = &operator==;<br>
> +  static_assert((*test_a)(A(), A()));<br>
> +}<br>
> +<br>
> +struct B1 {<br>
> +  virtual std::strong_ordering operator<=>(const B1&) const = default;<br>
> +};<br>
> +bool (B1::*test_b)(const B1&) const = &B1::operator==;<br>
> +<br>
> +struct C1 : B1 {<br>
> +  // OK, B1::operator== is virtual.<br>
> +  bool operator==(const B1&) const override;<br>
> +};<br>
> +<br>
> +struct B2 {<br>
> +  std::strong_ordering operator<=>(const B2&) const = default;<br>
> +};<br>
> +<br>
> +struct C2 : B2 {<br>
> +  bool operator==(const B2&) const override; // expected-error {{only virtual member functions}}<br>
> +};<br>
> +<br>
> +struct D {<br>
> +  std::strong_ordering operator<=>(const D&) const;<br>
> +  virtual std::strong_ordering operator<=>(const struct E&) const = 0;<br>
> +};<br>
> +struct E : D {<br>
> +  // expected-error@+2 {{only virtual member functions}}<br>
> +  // expected-note@+1 {{while declaring the corresponding implicit 'operator==' for this defaulted 'operator<=>'}}<br>
> +  std::strong_ordering operator<=>(const E&) const override = default;<br>
> +};<br>
> +<br>
> +struct F {<br>
> +  [[deprecated("oh no")]] std::strong_ordering operator<=>(const F&) const = default; // expected-note 4{{deprecated}}<br>
> +};<br>
> +void use_f(F f) {<br>
> +  void(f <=> f); // expected-warning {{oh no}}<br>
> +  void(f < f); // expected-warning {{oh no}}<br>
> +  void(f == f); // expected-warning {{oh no}}<br>
> +  void(f != f); // expected-warning {{oh no}}<br>
> +}<br>
> +<br>
> +class G {<br>
> +  // expected-note@+2 {{implicitly declared private here}}<br>
> +  // expected-note-re@+1 {{{{^}}declared private here}}<br>
> +  std::strong_ordering operator<=>(const G&) const = default;<br>
> +public:<br>
> +};<br>
> +void use_g(G g) {<br>
> +  void(g <=> g); // expected-error {{private}}<br>
> +  void(g == g); // expected-error {{private}}<br>
> +}<br>
> +<br>
> +struct H {<br>
> +  bool operator==(const H&) const; // expected-note {{here}}<br>
> +  constexpr std::strong_ordering operator<=>(const H&) const { return std::strong_ordering::equal; }<br>
> +};<br>
> +<br>
> +struct I {<br>
> +  H h; // expected-note {{used to compare}}<br>
> +  // expected-error@+1 {{defaulted definition of three-way comparison operator cannot be declared constexpr because the corresponding implicit 'operator==' invokes a non-constexpr comparison function}}<br>
> +  constexpr std::strong_ordering operator<=>(const I&) const = default;<br>
> +};<br>
> +<br>
> +struct J {<br>
> +  std::strong_ordering operator<=>(const J&) const & = default; // expected-note {{candidate function (the implicit 'operator==' for this 'operator<=>)'}}<br>
> +  friend std::strong_ordering operator<=>(const J&, const J&) = default; // expected-note {{candidate function (the implicit 'operator==' for this 'operator<=>)'}}<br>
> +};<br>
> +void use_j(J j) {<br>
> +  void(j == j); // expected-error {{ambiguous}}<br>
> +}<br>
> +<br>
> +namespace DeleteAfterFirstDecl {<br>
> +  bool operator==(const struct Q&, const struct Q&);<br>
> +  struct Q {<br>
> +    struct X {<br>
> +      friend std::strong_ordering operator<=>(const X&, const X&);<br>
> +    } x; // expected-note {{no viable comparison}}<br>
> +    // expected-error@+1 {{defaulting the corresponding implicit 'operator==' for this defaulted 'operator<=>' would delete it after its first declaration}}<br>
> +    friend std::strong_ordering operator<=>(const Q&, const Q&) = default;<br>
> +  };<br>
> +}<br>
> +<br>
> +// Note, substitution here results in the second parameter of 'operator=='<br>
> +// referring to the first parameter of 'operator==', not to the first parameter<br>
> +// of 'operator<=>'.<br>
> +// FIXME: Find a case where this matters (attribute enable_if?).<br>
> +struct K {<br>
> +  friend std::strong_ordering operator<=>(const K &k, decltype(k)) = default;<br>
> +};<br>
> +bool test_k = K() == K();<br>
> +<br>
> +namespace NoInjectionIfOperatorEqualsDec<wbr>lared {<br>
> +  struct A {<br>
> +    void operator==(int); // expected-note 2{{not viable}}<br>
> +    std::strong_ordering operator<=>(const A&) const = default;<br>
> +  };<br>
> +  bool test_a = A() == A(); // expected-error {{invalid operands}}<br>
> +<br>
> +  struct B {<br>
> +    friend void operator==(int, struct Q); // expected-note {{not viable}}<br>
> +    std::strong_ordering operator<=>(const B&) const = default;<br>
> +  };<br>
> +  bool test_b = B() == B(); // expected-error {{invalid operands}}<br>
> +<br>
> +  struct C {<br>
> +    void operator==(int); // expected-note 2{{not viable}}<br>
> +    friend std::strong_ordering operator<=>(const C&, const C&) = default;<br>
> +  };<br>
> +  bool test_c = C() == C(); // expected-error {{invalid operands}}<br>
> +<br>
> +  struct D {<br>
> +    void f() {<br>
> +      void operator==(const D&, int);<br>
> +    }<br>
> +    struct X {<br>
> +      friend void operator==(const D&, int);<br>
> +    };<br>
> +    friend std::strong_ordering operator<=>(const D&, const D&) = default;<br>
> +  };<br>
> +  bool test_d = D() == D();<br>
> +}<br>
><br>
> diff  --git a/clang/test/CodeGenCXX/cxx2a-<wbr>three-way-comparison.cpp b/clang/test/CodeGenCXX/cxx2a-<wbr>three-way-comparison.cpp<br>
> index e3c1535815f0..e6be640a1f7f 100644<br>
> --- a/clang/test/CodeGenCXX/cxx2a-<wbr>three-way-comparison.cpp<br>
> +++ b/clang/test/CodeGenCXX/cxx2a-<wbr>three-way-comparison.cpp<br>
> @@ -1,6 +1,41 @@<br>
> -// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=ITANIUM<br>
> -// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple x86_64-pc-win32 2>&1 | FileCheck %s --check-prefix=MSABI<br>
> -// RUN: not %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple -DBUILTIN 2>&1 | FileCheck %s --check-prefix=BUILTIN<br>
> +// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | FileCheck %s --check-prefixes=CHECK,ITANIUM<br>
> +// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple x86_64-pc-win32 2>&1 | FileCheck %s --check-prefixes=CHECK,MSABI<br>
> +<br>
> +namespace std {<br>
> +  struct strong_ordering {<br>
> +    int n;<br>
> +    constexpr operator int() const { return n; }<br>
> +    static const strong_ordering less, equal, greater;<br>
> +  };<br>
> +  constexpr strong_ordering strong_ordering::less = {-1};<br>
> +  constexpr strong_ordering strong_ordering::equal = {0};<br>
> +  constexpr strong_ordering strong_ordering::greater = {1};<br>
> +}<br>
> +<br>
> +struct Primary {<br>
> +  virtual void f();<br>
> +  std::strong_ordering operator<=>(const Primary&) const = default;<br>
> +};<br>
> +struct X {<br>
> +  virtual struct Y &operator=(Y&&);<br>
> +  virtual struct Y &operator=(const Y&);<br>
> +  std::strong_ordering operator<=>(const X&) const = default;<br>
> +};<br>
> +// The vtable for Y should contain the following entries in order:<br>
> +//  - Primary::f<br>
> +//  - Y::operator<=><br>
> +//  - Y::operator=(const Y&) (implicit)<br>
> +//  - Y::operator=(Y&&) (implicit)<br>
> +//  - Y::operator==(const Y&) const (implicit)<br>
> +// See:<br>
> +//   <a href="https://github.com/itanium-cxx-abi/cxx-abi/issues/83" rel="noreferrer" target="_blank">https://github.com/itanium-cx<wbr>x-abi/cxx-abi/issues/83</a> for assignment operator<br>
> +//   <a href="https://github.com/itanium-cxx-abi/cxx-abi/issues/88" rel="noreferrer" target="_blank">https://github.com/itanium-cx<wbr>x-abi/cxx-abi/issues/88</a> for equality comparison<br>
> +// FIXME: What rule does MSVC use?<br>
> +struct Y : Primary, X {<br>
> +  virtual std::strong_ordering operator<=>(const Y&) const = default;<br>
> +};<br>
> +Y y;<br>
> +// ITANIUM: @_ZTV1Y = {{.*}}constant {{.*}} null, {{.*}} @_ZTI1Y {{.*}} @_ZN7Primary1fEv {{.*}} @_ZNK1YssERKS_ {{.*}} @_ZN1YaSERKS_ {{.*}} @_ZN1YaSEOS_ {{.*}} @_ZNK1YeqERKS_ {{.*}} -{{4|8}} {{.*}} @_ZTI1Y {{.*}} @_ZThn8_N1YaSERKS_<br>
><br>
>  struct A {<br>
>    void operator<=>(int);<br>
> @@ -26,8 +61,11 @@ int f(A a) {<br>
>    return a <=> a;<br>
>  }<br>
><br>
> -#ifdef BUILTIN<br>
> -void builtin(int a) {<br>
> -  a <=> a; // BUILTIN: cannot compile this scalar expression yet<br>
> +// CHECK-LABEL: define {{.*}}builtin_cmp<br>
> +void builtin_cmp(int a) {<br>
> +  // CHECK: icmp slt<br>
> +  // CHECK: select<br>
> +  // CHECK: icmp eq<br>
> +  // CHECK: select<br>
> +  a <=> a;<br>
>  }<br>
> -#endif<br>
><br>
><br>
><br>
> ______________________________<wbr>_________________<br>
> cfe-commits mailing list<br>
> <a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a><br>
> <a href="https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits" rel="noreferrer" target="_blank">
https://lists.llvm.org/cgi-bin<wbr>/mailman/listinfo/cfe-commits</a><br>
______________________________<wbr>_________________<br>
cfe-commits mailing list<br>
<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a><br>
<a href="https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits" rel="noreferrer" target="_blank">https://lists.llvm.org/cgi-bin<wbr>/mailman/listinfo/cfe-commits</a>​<br>
</blockquote>
</div>
<p><br>
</p>
</body>
</html>