<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>