r375306 - [c++20] Add rewriting from comparison operators to <=> / ==.
Richard Smith via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 13 17:50:08 PST 2020
On Mon, 13 Jan 2020 at 15:12, Peter Collingbourne via cfe-commits <
cfe-commits at lists.llvm.org> wrote:
> On Fri, Oct 18, 2019 at 5:02 PM Richard Smith via cfe-commits <
> cfe-commits at lists.llvm.org> wrote:
>
>> Author: rsmith
>> Date: Fri Oct 18 17:04:43 2019
>> New Revision: 375306
>>
>> URL: http://llvm.org/viewvc/llvm-project?rev=375306&view=rev
>> Log:
>> [c++20] Add rewriting from comparison operators to <=> / ==.
>>
>> This adds support for rewriting <, >, <=, and >= to a normal or reversed
>> call to operator<=>, for rewriting != to a normal or reversed call to
>> operator==, and for rewriting <=> and == to reversed forms of those same
>> operators.
>>
>> Note that this is a breaking change for various C++17 code patterns,
>> including some in use in LLVM. The most common patterns (where an
>> operator== becomes ambiguous with a reversed form of itself) are still
>> accepted under this patch, as an extension (with a warning). I'm hopeful
>> that we can get the language rules fixed before C++20 ships, and the
>> extension warning is aimed primarily at providing data to inform that
>> decision.
>>
>
> Hi Richard, it looks like this change caused us to start rejecting the
> following input:
>
> struct S {
> bool operator==(int rhs) const;
> bool operator!=(int rhs) const;
> operator int() const;
> };
>
> void f(S a, S b) {
> a != b;
> }
>
> with:
>
> $ clang -std=gnu++2a -fsyntax-only /tmp/test.cpp
> /tmp/test.cpp:8:5: error: use of overloaded operator '!=' is ambiguous
> (with operand types 'S' and 'S')
> a != b;
> ~ ^ ~
> /tmp/test.cpp:2:8: note: candidate function (with reversed parameter order)
> bool operator==(int rhs) const;
> ^
> /tmp/test.cpp:3:8: note: candidate function
> bool operator!=(int rhs) const;
> ^
> /tmp/test.cpp:2:8: note: candidate function
> bool operator==(int rhs) const;
> ^
>
> It looks like this was intentional, but I wanted to make sure. The code
> pattern isn't very common (I see only one instance in Android), but I
> figured that you might want to be aware of it in case it would inform any
> changes to the language.
>
Thanks. Yes, this C++20 change is known to break some (hopefully mostly
rare) code patterns. I'd not seen the one above before, but I think that's
moderately likely to stay broken in C++20 onwards; the ambiguity between
non-reversed and reversed != seems real here (note that C++17 wouldn't have
allowed 0 != a, but C++20 does, which is why this became ambiguous). I'll
ask on the committee reflectors just in case.
Hopefully you're only seeing a build break under -std=c++2a? (If not,
that'd be a problem.)
> Peter
>
>
>> Added:
>>
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp
>>
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp
>>
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp
>> cfe/trunk/test/CodeGenCXX/mangle-cxx2a.cpp
>> Modified:
>> cfe/trunk/include/clang/AST/ExprCXX.h
>> cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
>> cfe/trunk/include/clang/Basic/OperatorKinds.h
>> cfe/trunk/include/clang/Sema/Overload.h
>> cfe/trunk/include/clang/Sema/Sema.h
>> cfe/trunk/lib/AST/ExprCXX.cpp
>> cfe/trunk/lib/Frontend/FrontendActions.cpp
>> cfe/trunk/lib/Sema/SemaExpr.cpp
>> cfe/trunk/lib/Sema/SemaOverload.cpp
>> cfe/trunk/lib/Sema/SemaTemplate.cpp
>> cfe/trunk/lib/Sema/SemaTemplateInstantiate.cpp
>> cfe/trunk/lib/Sema/TreeTransform.h
>> cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp
>> cfe/trunk/test/PCH/cxx2a-compare.cpp
>> cfe/trunk/test/SemaCXX/compare-cxx2a.cpp
>> cfe/trunk/test/SemaCXX/self-comparison.cpp
>> cfe/trunk/www/cxx_status.html
>>
>> Modified: cfe/trunk/include/clang/AST/ExprCXX.h
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/ExprCXX.h?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/include/clang/AST/ExprCXX.h (original)
>> +++ cfe/trunk/include/clang/AST/ExprCXX.h Fri Oct 18 17:04:43 2019
>> @@ -220,6 +220,40 @@ public:
>> }
>> };
>>
>> +/// Represents a call to a CUDA kernel function.
>> +class CUDAKernelCallExpr final : public CallExpr {
>> + friend class ASTStmtReader;
>> +
>> + enum { CONFIG, END_PREARG };
>> +
>> + // CUDAKernelCallExpr has some trailing objects belonging
>> + // to CallExpr. See CallExpr for the details.
>> +
>> + CUDAKernelCallExpr(Expr *Fn, CallExpr *Config, ArrayRef<Expr *> Args,
>> + QualType Ty, ExprValueKind VK, SourceLocation RP,
>> + unsigned MinNumArgs);
>> +
>> + CUDAKernelCallExpr(unsigned NumArgs, EmptyShell Empty);
>> +
>> +public:
>> + static CUDAKernelCallExpr *Create(const ASTContext &Ctx, Expr *Fn,
>> + CallExpr *Config, ArrayRef<Expr *>
>> Args,
>> + QualType Ty, ExprValueKind VK,
>> + SourceLocation RP, unsigned
>> MinNumArgs = 0);
>> +
>> + static CUDAKernelCallExpr *CreateEmpty(const ASTContext &Ctx,
>> + unsigned NumArgs, EmptyShell
>> Empty);
>> +
>> + const CallExpr *getConfig() const {
>> + return cast_or_null<CallExpr>(getPreArg(CONFIG));
>> + }
>> + CallExpr *getConfig() { return
>> cast_or_null<CallExpr>(getPreArg(CONFIG)); }
>> +
>> + static bool classof(const Stmt *T) {
>> + return T->getStmtClass() == CUDAKernelCallExprClass;
>> + }
>> +};
>> +
>> /// A rewritten comparison expression that was originally written using
>> /// operator syntax.
>> ///
>> @@ -310,40 +344,6 @@ public:
>> }
>> };
>>
>> -/// Represents a call to a CUDA kernel function.
>> -class CUDAKernelCallExpr final : public CallExpr {
>> - friend class ASTStmtReader;
>> -
>> - enum { CONFIG, END_PREARG };
>> -
>> - // CUDAKernelCallExpr has some trailing objects belonging
>> - // to CallExpr. See CallExpr for the details.
>> -
>> - CUDAKernelCallExpr(Expr *Fn, CallExpr *Config, ArrayRef<Expr *> Args,
>> - QualType Ty, ExprValueKind VK, SourceLocation RP,
>> - unsigned MinNumArgs);
>> -
>> - CUDAKernelCallExpr(unsigned NumArgs, EmptyShell Empty);
>> -
>> -public:
>> - static CUDAKernelCallExpr *Create(const ASTContext &Ctx, Expr *Fn,
>> - CallExpr *Config, ArrayRef<Expr *>
>> Args,
>> - QualType Ty, ExprValueKind VK,
>> - SourceLocation RP, unsigned
>> MinNumArgs = 0);
>> -
>> - static CUDAKernelCallExpr *CreateEmpty(const ASTContext &Ctx,
>> - unsigned NumArgs, EmptyShell
>> Empty);
>> -
>> - const CallExpr *getConfig() const {
>> - return cast_or_null<CallExpr>(getPreArg(CONFIG));
>> - }
>> - CallExpr *getConfig() { return
>> cast_or_null<CallExpr>(getPreArg(CONFIG)); }
>> -
>> - static bool classof(const Stmt *T) {
>> - return T->getStmtClass() == CUDAKernelCallExprClass;
>> - }
>> -};
>> -
>> /// Abstract class common to all of the C++ "named"/"keyword" casts.
>> ///
>> /// This abstract class is inherited by all of the classes
>>
>> Modified: cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td (original)
>> +++ cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td Fri Oct 18
>> 17:04:43 2019
>> @@ -3769,7 +3769,8 @@ def note_ovl_too_many_candidates : Note<
>> "pass -fshow-overloads=all to show them">;
>>
>> def select_ovl_candidate_kind : TextSubstitution<
>> - "%select{function|function|constructor|"
>> + "%select{function|function|function (with reversed parameter order)|"
>> + "constructor|"
>> "constructor (the implicit default constructor)|"
>> "constructor (the implicit copy constructor)|"
>> "constructor (the implicit move constructor)|"
>> @@ -3990,6 +3991,13 @@ def err_ovl_ambiguous_oper_unary : Error
>> "use of overloaded operator '%0' is ambiguous (operand type %1)">;
>> def err_ovl_ambiguous_oper_binary : Error<
>> "use of overloaded operator '%0' is ambiguous (with operand types %1
>> and %2)">;
>> +def ext_ovl_ambiguous_oper_binary_reversed : ExtWarn<
>> + "ISO C++20 considers use of overloaded operator '%0' (with operand
>> types %1 "
>> + "and %2) to be ambiguous despite there being a unique best viable
>> function">,
>> + InGroup<DiagGroup<"ambiguous-reversed-operator">>, SFINAEFailure;
>> +def note_ovl_ambiguous_oper_binary_reversed_candidate : Note<
>> + "ambiguity is between a regular call to this operator and a call with
>> the "
>> + "argument order reversed">;
>> def err_ovl_no_viable_oper : Error<"no viable overloaded '%0'">;
>> def note_assign_lhs_incomplete : Note<"type %0 is incomplete">;
>> def err_ovl_deleted_oper : Error<
>> @@ -3997,6 +4005,9 @@ def err_ovl_deleted_oper : Error<
>> def err_ovl_deleted_special_oper : Error<
>> "object of type %0 cannot be
>> %select{constructed|copied|moved|assigned|"
>> "assigned|destroyed}1 because its %sub{select_special_member_kind}1 is
>> implicitly deleted">;
>> +def err_ovl_rewrite_equalequal_not_bool : Error<
>> + "return type %0 of selected 'operator==' function for rewritten "
>> + "'%1' comparison is not 'bool'">;
>> def err_ovl_no_viable_subscript :
>> Error<"no viable overloaded operator[] for type %0">;
>> def err_ovl_no_oper :
>> @@ -9961,6 +9972,8 @@ def err_std_compare_type_not_supported :
>> "member '%2' is missing|"
>> "the type is not trivially copyable|"
>> "the type does not have the expected form}1">;
>> +def note_rewriting_operator_as_spaceship : Note<
>> + "while rewriting comparison as call to 'operator<=>' declared here">;
>>
>> // Memory Tagging Extensions (MTE) diagnostics
>> def err_memtag_arg_null_or_pointer : Error<
>>
>> Modified: cfe/trunk/include/clang/Basic/OperatorKinds.h
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/OperatorKinds.h?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/include/clang/Basic/OperatorKinds.h (original)
>> +++ cfe/trunk/include/clang/Basic/OperatorKinds.h Fri Oct 18 17:04:43 2019
>> @@ -30,6 +30,25 @@ enum OverloadedOperatorKind : int {
>> /// the preceding "operator" keyword.
>> const char *getOperatorSpelling(OverloadedOperatorKind Operator);
>>
>> +/// Get the other overloaded operator that the given operator can be
>> rewritten
>> +/// into, if any such operator exists.
>> +inline OverloadedOperatorKind
>> +getRewrittenOverloadedOperator(OverloadedOperatorKind Kind) {
>> + switch (Kind) {
>> + case OO_Less:
>> + case OO_LessEqual:
>> + case OO_Greater:
>> + case OO_GreaterEqual:
>> + return OO_Spaceship;
>> +
>> + case OO_ExclaimEqual:
>> + return OO_EqualEqual;
>> +
>> + default:
>> + return OO_None;
>> + }
>> +}
>> +
>> } // end namespace clang
>>
>> #endif
>>
>> Modified: cfe/trunk/include/clang/Sema/Overload.h
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Sema/Overload.h?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/include/clang/Sema/Overload.h (original)
>> +++ cfe/trunk/include/clang/Sema/Overload.h Fri Oct 18 17:04:43 2019
>> @@ -71,6 +71,30 @@ class Sema;
>> OCD_ViableCandidates
>> };
>>
>> + /// The parameter ordering that will be used for the candidate. This is
>> + /// used to represent C++20 binary operator rewrites that reverse the
>> order
>> + /// of the arguments. If the parameter ordering is Reversed, the Args
>> list is
>> + /// reversed (but obviously the ParamDecls for the function are not).
>> + ///
>> + /// After forming an OverloadCandidate with reversed parameters, the
>> list
>> + /// of conversions will (as always) be indexed by argument, so will be
>> + /// in reverse parameter order.
>> + enum class OverloadCandidateParamOrder : char { Normal, Reversed };
>> +
>> + /// The kinds of rewrite we perform on overload candidates. Note that
>> the
>> + /// values here are chosen to serve as both bitflags and as a rank
>> (lower
>> + /// values are preferred by overload resolution).
>> + enum OverloadCandidateRewriteKind : unsigned {
>> + /// Candidate is not a rewritten candidate.
>> + CRK_None = 0x0,
>> +
>> + /// Candidate is a rewritten candidate with a different operator
>> name.
>> + CRK_DifferentOperator = 0x1,
>> +
>> + /// Candidate is a rewritten candidate with a reversed order of
>> parameters.
>> + CRK_Reversed = 0x2,
>> + };
>> +
>> /// ImplicitConversionKind - The kind of implicit conversion used to
>> /// convert an argument to a parameter's type. The enumerator values
>> /// match with the table titled 'Conversions' in [over.ics.scs] and
>> are listed
>> @@ -757,7 +781,8 @@ class Sema;
>> CXXConversionDecl *Surrogate;
>>
>> /// The conversion sequences used to convert the function arguments
>> - /// to the function parameters.
>> + /// to the function parameters. Note that these are indexed by
>> argument,
>> + /// so may not match the parameter order of Function.
>> ConversionSequenceList Conversions;
>>
>> /// The FixIt hints which can be used to fix the Bad candidate.
>> @@ -783,6 +808,9 @@ class Sema;
>> /// True if the candidate was found using ADL.
>> CallExpr::ADLCallKind IsADLCandidate : 1;
>>
>> + /// Whether this is a rewritten candidate, and if so, of what kind?
>> + OverloadCandidateRewriteKind RewriteKind : 2;
>> +
>> /// FailureKind - The reason why this candidate is not viable.
>> /// Actually an OverloadFailureKind.
>> unsigned char FailureKind;
>> @@ -838,7 +866,8 @@ class Sema;
>>
>> private:
>> friend class OverloadCandidateSet;
>> - OverloadCandidate() : IsADLCandidate(CallExpr::NotADL) {}
>> + OverloadCandidate()
>> + : IsADLCandidate(CallExpr::NotADL), RewriteKind(CRK_None) {}
>> };
>>
>> /// OverloadCandidateSet - A set of overload candidates, used in C++
>> @@ -867,9 +896,54 @@ class Sema;
>> CSK_InitByConstructor,
>> };
>>
>> + /// Information about operator rewrites to consider when adding
>> operator
>> + /// functions to a candidate set.
>> + struct OperatorRewriteInfo {
>> + OperatorRewriteInfo()
>> + : OriginalOperator(OO_None), AllowRewrittenCandidates(false) {}
>> + OperatorRewriteInfo(OverloadedOperatorKind Op, bool AllowRewritten)
>> + : OriginalOperator(Op),
>> AllowRewrittenCandidates(AllowRewritten) {}
>> +
>> + /// The original operator as written in the source.
>> + OverloadedOperatorKind OriginalOperator;
>> + /// Whether we should include rewritten candidates in the overload
>> set.
>> + bool AllowRewrittenCandidates;
>> +
>> + /// Would use of this function result in a rewrite using a
>> different
>> + /// operator?
>> + bool isRewrittenOperator(const FunctionDecl *FD) {
>> + return OriginalOperator &&
>> + FD->getDeclName().getCXXOverloadedOperator() !=
>> OriginalOperator;
>> + }
>> +
>> + bool isAcceptableCandidate(const FunctionDecl *FD) {
>> + return AllowRewrittenCandidates || !isRewrittenOperator(FD);
>> + }
>> +
>> + /// Determine the kind of rewrite that should be performed for this
>> + /// candidate.
>> + OverloadCandidateRewriteKind
>> + getRewriteKind(const FunctionDecl *FD, OverloadCandidateParamOrder
>> PO) {
>> + OverloadCandidateRewriteKind CRK = CRK_None;
>> + if (isRewrittenOperator(FD))
>> + CRK = OverloadCandidateRewriteKind(CRK |
>> CRK_DifferentOperator);
>> + if (PO == OverloadCandidateParamOrder::Reversed)
>> + CRK = OverloadCandidateRewriteKind(CRK | CRK_Reversed);
>> + return CRK;
>> + }
>> +
>> + /// Determine whether we should consider looking for and adding
>> reversed
>> + /// candidates for operator Op.
>> + bool shouldAddReversed(OverloadedOperatorKind Op);
>> +
>> + /// Determine whether we should add a rewritten candidate for \p
>> FD with
>> + /// reversed parameter order.
>> + bool shouldAddReversed(ASTContext &Ctx, const FunctionDecl *FD);
>> + };
>> +
>> private:
>> SmallVector<OverloadCandidate, 16> Candidates;
>> - llvm::SmallPtrSet<Decl *, 16> Functions;
>> + llvm::SmallPtrSet<uintptr_t, 16> Functions;
>>
>> // Allocator for ConversionSequenceLists. We store the first few of
>> these
>> // inline to avoid allocation for small sets.
>> @@ -877,6 +951,7 @@ class Sema;
>>
>> SourceLocation Loc;
>> CandidateSetKind Kind;
>> + OperatorRewriteInfo RewriteInfo;
>>
>> constexpr static unsigned NumInlineBytes =
>> 24 * sizeof(ImplicitConversionSequence);
>> @@ -915,19 +990,24 @@ class Sema;
>> void destroyCandidates();
>>
>> public:
>> - OverloadCandidateSet(SourceLocation Loc, CandidateSetKind CSK)
>> - : Loc(Loc), Kind(CSK) {}
>> + OverloadCandidateSet(SourceLocation Loc, CandidateSetKind CSK,
>> + OperatorRewriteInfo RewriteInfo = {})
>> + : Loc(Loc), Kind(CSK), RewriteInfo(RewriteInfo) {}
>> OverloadCandidateSet(const OverloadCandidateSet &) = delete;
>> OverloadCandidateSet &operator=(const OverloadCandidateSet &) =
>> delete;
>> ~OverloadCandidateSet() { destroyCandidates(); }
>>
>> SourceLocation getLocation() const { return Loc; }
>> CandidateSetKind getKind() const { return Kind; }
>> + OperatorRewriteInfo getRewriteInfo() const { return RewriteInfo; }
>>
>> /// Determine when this overload candidate will be new to the
>> /// overload set.
>> - bool isNewCandidate(Decl *F) {
>> - return Functions.insert(F->getCanonicalDecl()).second;
>> + bool isNewCandidate(Decl *F, OverloadCandidateParamOrder PO =
>> +
>> OverloadCandidateParamOrder::Normal) {
>> + uintptr_t Key = reinterpret_cast<uintptr_t>(F->getCanonicalDecl());
>> + Key |= static_cast<uintptr_t>(PO);
>> + return Functions.insert(Key).second;
>> }
>>
>> /// Clear out all of the candidates.
>>
>> Modified: cfe/trunk/include/clang/Sema/Sema.h
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Sema/Sema.h?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/include/clang/Sema/Sema.h (original)
>> +++ cfe/trunk/include/clang/Sema/Sema.h Fri Oct 18 17:04:43 2019
>> @@ -159,6 +159,8 @@ namespace clang {
>> class OMPClause;
>> struct OMPVarListLocTy;
>> struct OverloadCandidate;
>> + enum class OverloadCandidateParamOrder : char;
>> + enum OverloadCandidateRewriteKind : unsigned;
>> class OverloadCandidateSet;
>> class OverloadExpr;
>> class ParenListExpr;
>> @@ -3019,7 +3021,8 @@ public:
>> bool AllowExplicit = true,
>> bool AllowExplicitConversion = false,
>> ADLCallKind IsADLCandidate =
>> ADLCallKind::NotADL,
>> - ConversionSequenceList EarlyConversions =
>> None);
>> + ConversionSequenceList EarlyConversions =
>> None,
>> + OverloadCandidateParamOrder PO = {});
>> void AddFunctionCandidates(const UnresolvedSetImpl &Functions,
>> ArrayRef<Expr *> Args,
>> OverloadCandidateSet &CandidateSet,
>> @@ -3032,7 +3035,8 @@ public:
>> Expr::Classification ObjectClassification,
>> ArrayRef<Expr *> Args,
>> OverloadCandidateSet& CandidateSet,
>> - bool SuppressUserConversion = false);
>> + bool SuppressUserConversion = false,
>> + OverloadCandidateParamOrder PO = {});
>> void AddMethodCandidate(CXXMethodDecl *Method,
>> DeclAccessPair FoundDecl,
>> CXXRecordDecl *ActingContext, QualType
>> ObjectType,
>> @@ -3041,7 +3045,8 @@ public:
>> OverloadCandidateSet& CandidateSet,
>> bool SuppressUserConversions = false,
>> bool PartialOverloading = false,
>> - ConversionSequenceList EarlyConversions =
>> None);
>> + ConversionSequenceList EarlyConversions = None,
>> + OverloadCandidateParamOrder PO = {});
>> void AddMethodTemplateCandidate(FunctionTemplateDecl *MethodTmpl,
>> DeclAccessPair FoundDecl,
>> CXXRecordDecl *ActingContext,
>> @@ -3051,23 +3056,22 @@ public:
>> ArrayRef<Expr *> Args,
>> OverloadCandidateSet& CandidateSet,
>> bool SuppressUserConversions = false,
>> - bool PartialOverloading = false);
>> + bool PartialOverloading = false,
>> + OverloadCandidateParamOrder PO = {});
>> void AddTemplateOverloadCandidate(
>> FunctionTemplateDecl *FunctionTemplate, DeclAccessPair FoundDecl,
>> TemplateArgumentListInfo *ExplicitTemplateArgs, ArrayRef<Expr *>
>> Args,
>> OverloadCandidateSet &CandidateSet, bool SuppressUserConversions =
>> false,
>> bool PartialOverloading = false, bool AllowExplicit = true,
>> - ADLCallKind IsADLCandidate = ADLCallKind::NotADL);
>> - bool CheckNonDependentConversions(FunctionTemplateDecl
>> *FunctionTemplate,
>> - ArrayRef<QualType> ParamTypes,
>> - ArrayRef<Expr *> Args,
>> - OverloadCandidateSet &CandidateSet,
>> - ConversionSequenceList &Conversions,
>> - bool SuppressUserConversions,
>> - CXXRecordDecl *ActingContext =
>> nullptr,
>> - QualType ObjectType = QualType(),
>> - Expr::Classification
>> - ObjectClassification = {});
>> + ADLCallKind IsADLCandidate = ADLCallKind::NotADL,
>> + OverloadCandidateParamOrder PO = {});
>> + bool CheckNonDependentConversions(
>> + FunctionTemplateDecl *FunctionTemplate, ArrayRef<QualType>
>> ParamTypes,
>> + ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
>> + ConversionSequenceList &Conversions, bool SuppressUserConversions,
>> + CXXRecordDecl *ActingContext = nullptr, QualType ObjectType =
>> QualType(),
>> + Expr::Classification ObjectClassification = {},
>> + OverloadCandidateParamOrder PO = {});
>> void AddConversionCandidate(
>> CXXConversionDecl *Conversion, DeclAccessPair FoundDecl,
>> CXXRecordDecl *ActingContext, Expr *From, QualType ToType,
>> @@ -3084,10 +3088,14 @@ public:
>> const FunctionProtoType *Proto,
>> Expr *Object, ArrayRef<Expr *> Args,
>> OverloadCandidateSet& CandidateSet);
>> + void AddNonMemberOperatorCandidates(
>> + const UnresolvedSetImpl &Functions, ArrayRef<Expr *> Args,
>> + OverloadCandidateSet &CandidateSet,
>> + TemplateArgumentListInfo *ExplicitTemplateArgs = nullptr);
>> void AddMemberOperatorCandidates(OverloadedOperatorKind Op,
>> SourceLocation OpLoc, ArrayRef<Expr
>> *> Args,
>> - OverloadCandidateSet& CandidateSet,
>> - SourceRange OpRange = SourceRange());
>> + OverloadCandidateSet &CandidateSet,
>> + OverloadCandidateParamOrder PO = {});
>> void AddBuiltinCandidate(QualType *ParamTys, ArrayRef<Expr *> Args,
>> OverloadCandidateSet& CandidateSet,
>> bool IsAssignmentOperator = false,
>> @@ -3103,9 +3111,10 @@ public:
>> bool PartialOverloading =
>> false);
>>
>> // Emit as a 'note' the specific overload candidate
>> - void NoteOverloadCandidate(NamedDecl *Found, FunctionDecl *Fn,
>> - QualType DestType = QualType(),
>> - bool TakingAddress = false);
>> + void NoteOverloadCandidate(
>> + NamedDecl *Found, FunctionDecl *Fn,
>> + OverloadCandidateRewriteKind RewriteKind =
>> OverloadCandidateRewriteKind(),
>> + QualType DestType = QualType(), bool TakingAddress = false);
>>
>> // Emit as a series of 'note's all template and non-templates
>> identified by
>> // the expression Expr
>> @@ -3237,7 +3246,8 @@ public:
>> BinaryOperatorKind Opc,
>> const UnresolvedSetImpl &Fns,
>> Expr *LHS, Expr *RHS,
>> - bool RequiresADL = true);
>> + bool RequiresADL = true,
>> + bool AllowRewrittenCandidates = true);
>>
>> ExprResult CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
>> SourceLocation RLoc,
>> @@ -7665,6 +7675,9 @@ public:
>> // We are substituting template arguments into a constraint
>> expression.
>> ConstraintSubstitution,
>>
>> + /// We are rewriting a comparison operator in terms of an
>> operator<=>.
>> + RewritingOperatorAsSpaceship,
>> +
>> /// Added for Template instantiation observation.
>> /// Memoization means we are _not_ instantiating a template because
>> /// it is already instantiated (but we entered a context where we
>>
>> Modified: cfe/trunk/lib/AST/ExprCXX.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/ExprCXX.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/AST/ExprCXX.cpp (original)
>> +++ cfe/trunk/lib/AST/ExprCXX.cpp Fri Oct 18 17:04:43 2019
>> @@ -79,7 +79,7 @@ CXXRewrittenBinaryOperator::getDecompose
>> Result.RHS = BO->getRHS();
>> Result.InnerBinOp = BO;
>> } else if (auto *BO = dyn_cast<CXXOperatorCallExpr>(E)) {
>> - assert(!SkippedNot || BO->getOperator() == OO_Equal);
>> + assert(!SkippedNot || BO->getOperator() == OO_EqualEqual);
>> assert(BO->isInfixBinaryOp());
>> switch (BO->getOperator()) {
>> case OO_Less: Result.Opcode = BO_LT; break;
>> @@ -107,7 +107,7 @@ CXXRewrittenBinaryOperator::getDecompose
>> return Result;
>>
>> // Otherwise, we expect a <=> to now be on the LHS.
>> - E = Result.InnerBinOp->IgnoreImplicit();
>> + E = Result.LHS->IgnoreImplicit();
>> if (auto *BO = dyn_cast<BinaryOperator>(E)) {
>> assert(BO->getOpcode() == BO_Cmp);
>> Result.LHS = BO->getLHS();
>>
>> Modified: cfe/trunk/lib/Frontend/FrontendActions.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Frontend/FrontendActions.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/Frontend/FrontendActions.cpp (original)
>> +++ cfe/trunk/lib/Frontend/FrontendActions.cpp Fri Oct 18 17:04:43 2019
>> @@ -415,6 +415,8 @@ private:
>> return "DeclaringSpecialMember";
>> case CodeSynthesisContext::DefiningSynthesizedFunction:
>> return "DefiningSynthesizedFunction";
>> + case CodeSynthesisContext::RewritingOperatorAsSpaceship:
>> + return "RewritingOperatorAsSpaceship";
>> case CodeSynthesisContext::Memoization:
>> return "Memoization";
>> case CodeSynthesisContext::ConstraintsCheck:
>>
>> Modified: cfe/trunk/lib/Sema/SemaExpr.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaExpr.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/Sema/SemaExpr.cpp (original)
>> +++ cfe/trunk/lib/Sema/SemaExpr.cpp Fri Oct 18 17:04:43 2019
>> @@ -13310,6 +13310,13 @@ static ExprResult BuildOverloadedBinOp(S
>> S.LookupOverloadedOperatorName(OverOp, Sc, LHS->getType(),
>> RHS->getType(), Functions);
>>
>> + // In C++20 onwards, we may have a second operator to look up.
>> + if (S.getLangOpts().CPlusPlus2a) {
>> + if (OverloadedOperatorKind ExtraOp =
>> getRewrittenOverloadedOperator(OverOp))
>> + S.LookupOverloadedOperatorName(ExtraOp, Sc, LHS->getType(),
>> + RHS->getType(), Functions);
>> + }
>> +
>> // Build the (potentially-overloaded, potentially-dependent)
>> // binary operation.
>> return S.CreateOverloadedBinOp(OpLoc, Opc, Functions, LHS, RHS);
>>
>> Modified: cfe/trunk/lib/Sema/SemaOverload.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaOverload.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/Sema/SemaOverload.cpp (original)
>> +++ cfe/trunk/lib/Sema/SemaOverload.cpp Fri Oct 18 17:04:43 2019
>> @@ -848,6 +848,25 @@ llvm::Optional<unsigned> DeductionFailur
>> }
>> }
>>
>> +bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
>> + OverloadedOperatorKind Op) {
>> + if (!AllowRewrittenCandidates)
>> + return false;
>> + return Op == OO_EqualEqual || Op == OO_Spaceship;
>> +}
>> +
>> +bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
>> + ASTContext &Ctx, const FunctionDecl *FD) {
>> + if (!shouldAddReversed(FD->getDeclName().getCXXOverloadedOperator()))
>> + return false;
>> + // Don't bother adding a reversed candidate that can never be a better
>> + // match than the non-reversed version.
>> + return FD->getNumParams() != 2 ||
>> + !Ctx.hasSameUnqualifiedType(FD->getParamDecl(0)->getType(),
>> + FD->getParamDecl(1)->getType()) ||
>> + FD->hasAttr<EnableIfAttr>();
>> +}
>> +
>> void OverloadCandidateSet::destroyCandidates() {
>> for (iterator i = begin(), e = end(); i != e; ++i) {
>> for (auto &C : i->Conversions)
>> @@ -6056,7 +6075,8 @@ void Sema::AddOverloadCandidate(
>> FunctionDecl *Function, DeclAccessPair FoundDecl, ArrayRef<Expr *>
>> Args,
>> OverloadCandidateSet &CandidateSet, bool SuppressUserConversions,
>> bool PartialOverloading, bool AllowExplicit, bool
>> AllowExplicitConversions,
>> - ADLCallKind IsADLCandidate, ConversionSequenceList EarlyConversions)
>> {
>> + ADLCallKind IsADLCandidate, ConversionSequenceList EarlyConversions,
>> + OverloadCandidateParamOrder PO) {
>> const FunctionProtoType *Proto
>> =
>> dyn_cast<FunctionProtoType>(Function->getType()->getAs<FunctionType>());
>> assert(Proto && "Functions without a prototype cannot be overloaded");
>> @@ -6075,25 +6095,14 @@ void Sema::AddOverloadCandidate(
>> AddMethodCandidate(Method, FoundDecl, Method->getParent(),
>> QualType(),
>> Expr::Classification::makeSimpleLValue(), Args,
>> CandidateSet, SuppressUserConversions,
>> - PartialOverloading, EarlyConversions);
>> + PartialOverloading, EarlyConversions, PO);
>> return;
>> }
>> // We treat a constructor like a non-member function, since its
>> object
>> // argument doesn't participate in overload resolution.
>> }
>>
>> - if (!CandidateSet.isNewCandidate(Function))
>> - return;
>> -
>> - // C++ [over.match.oper]p3:
>> - // if no operand has a class type, only those non-member functions
>> in the
>> - // lookup set that have a first parameter of type T1 or "reference to
>> - // (possibly cv-qualified) T1", when T1 is an enumeration type, or
>> (if there
>> - // is a right operand) a second parameter of type T2 or "reference to
>> - // (possibly cv-qualified) T2", when T2 is an enumeration type, are
>> - // candidate functions.
>> - if (CandidateSet.getKind() == OverloadCandidateSet::CSK_Operator &&
>> - !IsAcceptableNonMemberOperatorCandidate(Context, Function, Args))
>> + if (!CandidateSet.isNewCandidate(Function, PO))
>> return;
>>
>> // C++11 [class.copy]p11: [DR1402]
>> @@ -6108,12 +6117,25 @@ void Sema::AddOverloadCandidate(
>> EnterExpressionEvaluationContext Unevaluated(
>> *this, Sema::ExpressionEvaluationContext::Unevaluated);
>>
>> + // C++ [over.match.oper]p3:
>> + // if no operand has a class type, only those non-member functions
>> in the
>> + // lookup set that have a first parameter of type T1 or "reference to
>> + // (possibly cv-qualified) T1", when T1 is an enumeration type, or
>> (if there
>> + // is a right operand) a second parameter of type T2 or "reference to
>> + // (possibly cv-qualified) T2", when T2 is an enumeration type, are
>> + // candidate functions.
>> + if (CandidateSet.getKind() == OverloadCandidateSet::CSK_Operator &&
>> + !IsAcceptableNonMemberOperatorCandidate(Context, Function, Args))
>> + return;
>> +
>> // Add this candidate
>> OverloadCandidate &Candidate =
>> CandidateSet.addCandidate(Args.size(), EarlyConversions);
>> Candidate.FoundDecl = FoundDecl;
>> Candidate.Function = Function;
>> Candidate.Viable = true;
>> + Candidate.RewriteKind =
>> + CandidateSet.getRewriteInfo().getRewriteKind(Function, PO);
>> Candidate.IsSurrogate = false;
>> Candidate.IsADLCandidate = IsADLCandidate;
>> Candidate.IgnoreObjectArgument = false;
>> @@ -6213,7 +6235,9 @@ void Sema::AddOverloadCandidate(
>> // Determine the implicit conversion sequences for each of the
>> // arguments.
>> for (unsigned ArgIdx = 0; ArgIdx < Args.size(); ++ArgIdx) {
>> - if (Candidate.Conversions[ArgIdx].isInitialized()) {
>> + unsigned ConvIdx =
>> + PO == OverloadCandidateParamOrder::Reversed ? 1 - ArgIdx :
>> ArgIdx;
>> + if (Candidate.Conversions[ConvIdx].isInitialized()) {
>> // We already formed a conversion sequence for this parameter
>> during
>> // template argument deduction.
>> } else if (ArgIdx < NumParams) {
>> @@ -6222,12 +6246,12 @@ void Sema::AddOverloadCandidate(
>> // (13.3.3.1) that converts that argument to the corresponding
>> // parameter of F.
>> QualType ParamType = Proto->getParamType(ArgIdx);
>> - Candidate.Conversions[ArgIdx] = TryCopyInitialization(
>> + Candidate.Conversions[ConvIdx] = TryCopyInitialization(
>> *this, Args[ArgIdx], ParamType, SuppressUserConversions,
>> /*InOverloadResolution=*/true,
>> /*AllowObjCWritebackConversion=*/
>> getLangOpts().ObjCAutoRefCount, AllowExplicitConversions);
>> - if (Candidate.Conversions[ArgIdx].isBad()) {
>> + if (Candidate.Conversions[ConvIdx].isBad()) {
>> Candidate.Viable = false;
>> Candidate.FailureKind = ovl_fail_bad_conversion;
>> return;
>> @@ -6236,7 +6260,7 @@ void Sema::AddOverloadCandidate(
>> // (C++ 13.3.2p2): For the purposes of overload resolution, any
>> // argument for which there is no corresponding parameter is
>> // considered to ""match the ellipsis" (C+ 13.3.3.1.3).
>> - Candidate.Conversions[ArgIdx].setEllipsis();
>> + Candidate.Conversions[ConvIdx].setEllipsis();
>> }
>> }
>>
>> @@ -6583,9 +6607,10 @@ void Sema::AddFunctionCandidates(const U
>> FunctionArgs = Args.slice(1);
>> }
>> if (FunTmpl) {
>> - AddTemplateOverloadCandidate(
>> - FunTmpl, F.getPair(), ExplicitTemplateArgs, FunctionArgs,
>> - CandidateSet, SuppressUserConversions, PartialOverloading);
>> + AddTemplateOverloadCandidate(FunTmpl, F.getPair(),
>> + ExplicitTemplateArgs, FunctionArgs,
>> + CandidateSet,
>> SuppressUserConversions,
>> + PartialOverloading);
>> } else {
>> AddOverloadCandidate(FD, F.getPair(), FunctionArgs, CandidateSet,
>> SuppressUserConversions,
>> PartialOverloading);
>> @@ -6596,12 +6621,12 @@ void Sema::AddFunctionCandidates(const U
>>
>> /// AddMethodCandidate - Adds a named decl (which is some kind of
>> /// method) as a method candidate to the given overload set.
>> -void Sema::AddMethodCandidate(DeclAccessPair FoundDecl,
>> - QualType ObjectType,
>> +void Sema::AddMethodCandidate(DeclAccessPair FoundDecl, QualType
>> ObjectType,
>> Expr::Classification ObjectClassification,
>> ArrayRef<Expr *> Args,
>> - OverloadCandidateSet& CandidateSet,
>> - bool SuppressUserConversions) {
>> + OverloadCandidateSet &CandidateSet,
>> + bool SuppressUserConversions,
>> + OverloadCandidateParamOrder PO) {
>> NamedDecl *Decl = FoundDecl.getDecl();
>> CXXRecordDecl *ActingContext =
>> cast<CXXRecordDecl>(Decl->getDeclContext());
>>
>> @@ -6614,11 +6639,11 @@ void Sema::AddMethodCandidate(DeclAccess
>> AddMethodTemplateCandidate(TD, FoundDecl, ActingContext,
>> /*ExplicitArgs*/ nullptr, ObjectType,
>> ObjectClassification, Args, CandidateSet,
>> - SuppressUserConversions);
>> + SuppressUserConversions, false, PO);
>> } else {
>> AddMethodCandidate(cast<CXXMethodDecl>(Decl), FoundDecl,
>> ActingContext,
>> ObjectType, ObjectClassification, Args,
>> CandidateSet,
>> - SuppressUserConversions);
>> + SuppressUserConversions, false, None, PO);
>> }
>> }
>>
>> @@ -6637,14 +6662,15 @@ Sema::AddMethodCandidate(CXXMethodDecl *
>> OverloadCandidateSet &CandidateSet,
>> bool SuppressUserConversions,
>> bool PartialOverloading,
>> - ConversionSequenceList EarlyConversions) {
>> + ConversionSequenceList EarlyConversions,
>> + OverloadCandidateParamOrder PO) {
>> const FunctionProtoType *Proto
>> =
>> dyn_cast<FunctionProtoType>(Method->getType()->getAs<FunctionType>());
>> assert(Proto && "Methods without a prototype cannot be overloaded");
>> assert(!isa<CXXConstructorDecl>(Method) &&
>> "Use AddOverloadCandidate for constructors");
>>
>> - if (!CandidateSet.isNewCandidate(Method))
>> + if (!CandidateSet.isNewCandidate(Method, PO))
>> return;
>>
>> // C++11 [class.copy]p23: [DR1402]
>> @@ -6663,6 +6689,8 @@ Sema::AddMethodCandidate(CXXMethodDecl *
>> CandidateSet.addCandidate(Args.size() + 1, EarlyConversions);
>> Candidate.FoundDecl = FoundDecl;
>> Candidate.Function = Method;
>> + Candidate.RewriteKind =
>> + CandidateSet.getRewriteInfo().getRewriteKind(Method, PO);
>> Candidate.IsSurrogate = false;
>> Candidate.IgnoreObjectArgument = false;
>> Candidate.ExplicitCallArguments = Args.size();
>> @@ -6698,12 +6726,13 @@ Sema::AddMethodCandidate(CXXMethodDecl *
>> // The implicit object argument is ignored.
>> Candidate.IgnoreObjectArgument = true;
>> else {
>> + unsigned ConvIdx = PO == OverloadCandidateParamOrder::Reversed ? 1 :
>> 0;
>> // Determine the implicit conversion sequence for the object
>> // parameter.
>> - Candidate.Conversions[0] = TryObjectArgumentInitialization(
>> + Candidate.Conversions[ConvIdx] = TryObjectArgumentInitialization(
>> *this, CandidateSet.getLocation(), ObjectType,
>> ObjectClassification,
>> Method, ActingContext);
>> - if (Candidate.Conversions[0].isBad()) {
>> + if (Candidate.Conversions[ConvIdx].isBad()) {
>> Candidate.Viable = false;
>> Candidate.FailureKind = ovl_fail_bad_conversion;
>> return;
>> @@ -6722,7 +6751,9 @@ Sema::AddMethodCandidate(CXXMethodDecl *
>> // Determine the implicit conversion sequences for each of the
>> // arguments.
>> for (unsigned ArgIdx = 0; ArgIdx < Args.size(); ++ArgIdx) {
>> - if (Candidate.Conversions[ArgIdx + 1].isInitialized()) {
>> + unsigned ConvIdx =
>> + PO == OverloadCandidateParamOrder::Reversed ? 0 : (ArgIdx + 1);
>> + if (Candidate.Conversions[ConvIdx].isInitialized()) {
>> // We already formed a conversion sequence for this parameter
>> during
>> // template argument deduction.
>> } else if (ArgIdx < NumParams) {
>> @@ -6731,13 +6762,13 @@ Sema::AddMethodCandidate(CXXMethodDecl *
>> // (13.3.3.1) that converts that argument to the corresponding
>> // parameter of F.
>> QualType ParamType = Proto->getParamType(ArgIdx);
>> - Candidate.Conversions[ArgIdx + 1]
>> + Candidate.Conversions[ConvIdx]
>> = TryCopyInitialization(*this, Args[ArgIdx], ParamType,
>> SuppressUserConversions,
>> /*InOverloadResolution=*/true,
>> /*AllowObjCWritebackConversion=*/
>> getLangOpts().ObjCAutoRefCount);
>> - if (Candidate.Conversions[ArgIdx + 1].isBad()) {
>> + if (Candidate.Conversions[ConvIdx].isBad()) {
>> Candidate.Viable = false;
>> Candidate.FailureKind = ovl_fail_bad_conversion;
>> return;
>> @@ -6746,7 +6777,7 @@ Sema::AddMethodCandidate(CXXMethodDecl *
>> // (C++ 13.3.2p2): For the purposes of overload resolution, any
>> // argument for which there is no corresponding parameter is
>> // considered to "match the ellipsis" (C+ 13.3.3.1.3).
>> - Candidate.Conversions[ArgIdx + 1].setEllipsis();
>> + Candidate.Conversions[ConvIdx].setEllipsis();
>> }
>> }
>>
>> @@ -6767,18 +6798,14 @@ Sema::AddMethodCandidate(CXXMethodDecl *
>> /// Add a C++ member function template as a candidate to the candidate
>> /// set, using template argument deduction to produce an appropriate
>> member
>> /// function template specialization.
>> -void
>> -Sema::AddMethodTemplateCandidate(FunctionTemplateDecl *MethodTmpl,
>> - DeclAccessPair FoundDecl,
>> - CXXRecordDecl *ActingContext,
>> - TemplateArgumentListInfo
>> *ExplicitTemplateArgs,
>> - QualType ObjectType,
>> - Expr::Classification
>> ObjectClassification,
>> - ArrayRef<Expr *> Args,
>> - OverloadCandidateSet& CandidateSet,
>> - bool SuppressUserConversions,
>> - bool PartialOverloading) {
>> - if (!CandidateSet.isNewCandidate(MethodTmpl))
>> +void Sema::AddMethodTemplateCandidate(
>> + FunctionTemplateDecl *MethodTmpl, DeclAccessPair FoundDecl,
>> + CXXRecordDecl *ActingContext,
>> + TemplateArgumentListInfo *ExplicitTemplateArgs, QualType ObjectType,
>> + Expr::Classification ObjectClassification, ArrayRef<Expr *> Args,
>> + OverloadCandidateSet &CandidateSet, bool SuppressUserConversions,
>> + bool PartialOverloading, OverloadCandidateParamOrder PO) {
>> + if (!CandidateSet.isNewCandidate(MethodTmpl, PO))
>> return;
>>
>> // C++ [over.match.funcs]p7:
>> @@ -6799,13 +6826,15 @@ Sema::AddMethodTemplateCandidate(Functio
>> return CheckNonDependentConversions(
>> MethodTmpl, ParamTypes, Args, CandidateSet, Conversions,
>> SuppressUserConversions, ActingContext, ObjectType,
>> - ObjectClassification);
>> + ObjectClassification, PO);
>> })) {
>> OverloadCandidate &Candidate =
>> CandidateSet.addCandidate(Conversions.size(), Conversions);
>> Candidate.FoundDecl = FoundDecl;
>> Candidate.Function = MethodTmpl->getTemplatedDecl();
>> Candidate.Viable = false;
>> + Candidate.RewriteKind =
>> + CandidateSet.getRewriteInfo().getRewriteKind(Candidate.Function,
>> PO);
>> Candidate.IsSurrogate = false;
>> Candidate.IgnoreObjectArgument =
>> cast<CXXMethodDecl>(Candidate.Function)->isStatic() ||
>> @@ -6829,7 +6858,7 @@ Sema::AddMethodTemplateCandidate(Functio
>> AddMethodCandidate(cast<CXXMethodDecl>(Specialization), FoundDecl,
>> ActingContext, ObjectType, ObjectClassification,
>> Args,
>> CandidateSet, SuppressUserConversions,
>> PartialOverloading,
>> - Conversions);
>> + Conversions, PO);
>> }
>>
>> /// Add a C++ function template specialization as a candidate
>> @@ -6839,8 +6868,9 @@ void Sema::AddTemplateOverloadCandidate(
>> FunctionTemplateDecl *FunctionTemplate, DeclAccessPair FoundDecl,
>> TemplateArgumentListInfo *ExplicitTemplateArgs, ArrayRef<Expr *>
>> Args,
>> OverloadCandidateSet &CandidateSet, bool SuppressUserConversions,
>> - bool PartialOverloading, bool AllowExplicit, ADLCallKind
>> IsADLCandidate) {
>> - if (!CandidateSet.isNewCandidate(FunctionTemplate))
>> + bool PartialOverloading, bool AllowExplicit, ADLCallKind
>> IsADLCandidate,
>> + OverloadCandidateParamOrder PO) {
>> + if (!CandidateSet.isNewCandidate(FunctionTemplate, PO))
>> return;
>>
>> // C++ [over.match.funcs]p7:
>> @@ -6858,15 +6888,17 @@ void Sema::AddTemplateOverloadCandidate(
>> if (TemplateDeductionResult Result = DeduceTemplateArguments(
>> FunctionTemplate, ExplicitTemplateArgs, Args, Specialization,
>> Info,
>> PartialOverloading, [&](ArrayRef<QualType> ParamTypes) {
>> - return CheckNonDependentConversions(FunctionTemplate,
>> ParamTypes,
>> - Args, CandidateSet,
>> Conversions,
>> - SuppressUserConversions);
>> + return CheckNonDependentConversions(
>> + FunctionTemplate, ParamTypes, Args, CandidateSet,
>> Conversions,
>> + SuppressUserConversions, nullptr, QualType(), {}, PO);
>> })) {
>> OverloadCandidate &Candidate =
>> CandidateSet.addCandidate(Conversions.size(), Conversions);
>> Candidate.FoundDecl = FoundDecl;
>> Candidate.Function = FunctionTemplate->getTemplatedDecl();
>> Candidate.Viable = false;
>> + Candidate.RewriteKind =
>> + CandidateSet.getRewriteInfo().getRewriteKind(Candidate.Function,
>> PO);
>> Candidate.IsSurrogate = false;
>> Candidate.IsADLCandidate = IsADLCandidate;
>> // Ignore the object argument if there is one, since we don't have
>> an object
>> @@ -6891,7 +6923,7 @@ void Sema::AddTemplateOverloadCandidate(
>> AddOverloadCandidate(
>> Specialization, FoundDecl, Args, CandidateSet,
>> SuppressUserConversions,
>> PartialOverloading, AllowExplicit,
>> - /*AllowExplicitConversions*/ false, IsADLCandidate, Conversions);
>> + /*AllowExplicitConversions*/ false, IsADLCandidate, Conversions,
>> PO);
>> }
>>
>> /// Check that implicit conversion sequences can be formed for each
>> argument
>> @@ -6902,7 +6934,7 @@ bool Sema::CheckNonDependentConversions(
>> ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
>> ConversionSequenceList &Conversions, bool SuppressUserConversions,
>> CXXRecordDecl *ActingContext, QualType ObjectType,
>> - Expr::Classification ObjectClassification) {
>> + Expr::Classification ObjectClassification,
>> OverloadCandidateParamOrder PO) {
>> // FIXME: The cases in which we allow explicit conversions for
>> constructor
>> // arguments never consider calling a constructor template. It's not
>> clear
>> // that is correct.
>> @@ -6925,10 +6957,11 @@ bool Sema::CheckNonDependentConversions(
>> // overload resolution is permitted to sidestep instantiations.
>> if (HasThisConversion && !cast<CXXMethodDecl>(FD)->isStatic() &&
>> !ObjectType.isNull()) {
>> - Conversions[0] = TryObjectArgumentInitialization(
>> + unsigned ConvIdx = PO == OverloadCandidateParamOrder::Reversed ? 1 :
>> 0;
>> + Conversions[ConvIdx] = TryObjectArgumentInitialization(
>> *this, CandidateSet.getLocation(), ObjectType,
>> ObjectClassification,
>> Method, ActingContext);
>> - if (Conversions[0].isBad())
>> + if (Conversions[ConvIdx].isBad())
>> return true;
>> }
>>
>> @@ -6936,14 +6969,17 @@ bool Sema::CheckNonDependentConversions(
>> ++I) {
>> QualType ParamType = ParamTypes[I];
>> if (!ParamType->isDependentType()) {
>> - Conversions[ThisConversions + I]
>> + unsigned ConvIdx = PO == OverloadCandidateParamOrder::Reversed
>> + ? 0
>> + : (ThisConversions + I);
>> + Conversions[ConvIdx]
>> = TryCopyInitialization(*this, Args[I], ParamType,
>> SuppressUserConversions,
>> /*InOverloadResolution=*/true,
>> /*AllowObjCWritebackConversion=*/
>> getLangOpts().ObjCAutoRefCount,
>> AllowExplicit);
>> - if (Conversions[ThisConversions + I].isBad())
>> + if (Conversions[ConvIdx].isBad())
>> return true;
>> }
>> }
>> @@ -7331,6 +7367,48 @@ void Sema::AddSurrogateCandidate(CXXConv
>> }
>> }
>>
>> +/// Add all of the non-member operator function declarations in the given
>> +/// function set to the overload candidate set.
>> +void Sema::AddNonMemberOperatorCandidates(
>> + const UnresolvedSetImpl &Fns, ArrayRef<Expr *> Args,
>> + OverloadCandidateSet &CandidateSet,
>> + TemplateArgumentListInfo *ExplicitTemplateArgs) {
>> + for (UnresolvedSetIterator F = Fns.begin(), E = Fns.end(); F != E;
>> ++F) {
>> + NamedDecl *D = F.getDecl()->getUnderlyingDecl();
>> + ArrayRef<Expr *> FunctionArgs = Args;
>> +
>> + FunctionTemplateDecl *FunTmpl = dyn_cast<FunctionTemplateDecl>(D);
>> + FunctionDecl *FD =
>> + FunTmpl ? FunTmpl->getTemplatedDecl() : cast<FunctionDecl>(D);
>> +
>> + // Don't consider rewritten functions if we're not rewriting.
>> + if (!CandidateSet.getRewriteInfo().isAcceptableCandidate(FD))
>> + continue;
>> +
>> + assert(!isa<CXXMethodDecl>(FD) &&
>> + "unqualified operator lookup found a member function");
>> +
>> + if (FunTmpl) {
>> + AddTemplateOverloadCandidate(FunTmpl, F.getPair(),
>> ExplicitTemplateArgs,
>> + FunctionArgs, CandidateSet);
>> + if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
>> + AddTemplateOverloadCandidate(
>> + FunTmpl, F.getPair(), ExplicitTemplateArgs,
>> + {FunctionArgs[1], FunctionArgs[0]}, CandidateSet, false,
>> false,
>> + true, ADLCallKind::NotADL,
>> OverloadCandidateParamOrder::Reversed);
>> + } else {
>> + if (ExplicitTemplateArgs)
>> + continue;
>> + AddOverloadCandidate(FD, F.getPair(), FunctionArgs, CandidateSet);
>> + if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
>> + AddOverloadCandidate(FD, F.getPair(),
>> + {FunctionArgs[1], FunctionArgs[0]},
>> CandidateSet,
>> + false, false, true, false,
>> ADLCallKind::NotADL,
>> + None,
>> OverloadCandidateParamOrder::Reversed);
>> + }
>> + }
>> +}
>> +
>> /// Add overload candidates for overloaded operators that are
>> /// member functions.
>> ///
>> @@ -7342,8 +7420,8 @@ void Sema::AddSurrogateCandidate(CXXConv
>> void Sema::AddMemberOperatorCandidates(OverloadedOperatorKind Op,
>> SourceLocation OpLoc,
>> ArrayRef<Expr *> Args,
>> - OverloadCandidateSet&
>> CandidateSet,
>> - SourceRange OpRange) {
>> + OverloadCandidateSet
>> &CandidateSet,
>> + OverloadCandidateParamOrder PO) {
>> DeclarationName OpName =
>> Context.DeclarationNames.getCXXOperatorName(Op);
>>
>> // C++ [over.match.oper]p3:
>> @@ -7378,7 +7456,7 @@ void Sema::AddMemberOperatorCandidates(O
>> ++Oper)
>> AddMethodCandidate(Oper.getPair(), Args[0]->getType(),
>> Args[0]->Classify(Context), Args.slice(1),
>> - CandidateSet, /*SuppressUserConversion=*/false);
>> + CandidateSet, /*SuppressUserConversion=*/false,
>> PO);
>> }
>> }
>>
>> @@ -8183,10 +8261,16 @@ public:
>> if (C->Function->isFunctionTemplateSpecialization())
>> continue;
>>
>> - QualType FirstParamType =
>> - C->Function->getParamDecl(0)->getType().getUnqualifiedType();
>> - QualType SecondParamType =
>> - C->Function->getParamDecl(1)->getType().getUnqualifiedType();
>> + // We interpret "same parameter-type-list" as applying to the
>> + // "synthesized candidate, with the order of the two parameters
>> + // reversed", not to the original function.
>> + bool Reversed = C->RewriteKind & CRK_Reversed;
>> + QualType FirstParamType = C->Function->getParamDecl(Reversed ?
>> 1 : 0)
>> + ->getType()
>> + .getUnqualifiedType();
>> + QualType SecondParamType = C->Function->getParamDecl(Reversed
>> ? 0 : 1)
>> + ->getType()
>> + .getUnqualifiedType();
>>
>> // Skip if either parameter isn't of enumeral type.
>> if (!FirstParamType->isEnumeralType() ||
>> @@ -9240,6 +9324,7 @@ bool clang::isBetterOverloadCandidate(
>> // A viable function F1 is defined to be a better function than
>> another
>> // viable function F2 if for all arguments i, ICSi(F1) is not a worse
>> // conversion sequence than ICSi(F2), and then...
>> + bool HasWorseConversion = false;
>> for (unsigned ArgIdx = StartArg; ArgIdx < NumArgs; ++ArgIdx) {
>> switch (CompareImplicitConversionSequences(S, Loc,
>> Cand1.Conversions[ArgIdx],
>> @@ -9250,6 +9335,24 @@ bool clang::isBetterOverloadCandidate(
>> break;
>>
>> case ImplicitConversionSequence::Worse:
>> + if (Cand1.Function && Cand1.Function == Cand2.Function &&
>> + (Cand2.RewriteKind & CRK_Reversed) != 0) {
>> + // Work around large-scale breakage caused by considering
>> reversed
>> + // forms of operator== in C++20:
>> + //
>> + // When comparing a function against its reversed form, if we
>> have a
>> + // better conversion for one argument and a worse conversion for
>> the
>> + // other, we prefer the non-reversed form.
>> + //
>> + // This prevents a conversion function from being considered
>> ambiguous
>> + // with its own reversed form in various where it's only
>> incidentally
>> + // heterogeneous.
>> + //
>> + // We diagnose this as an extension from CreateOverloadedBinOp.
>> + HasWorseConversion = true;
>> + break;
>> + }
>> +
>> // Cand1 can't be better than Cand2.
>> return false;
>>
>> @@ -9263,6 +9366,8 @@ bool clang::isBetterOverloadCandidate(
>> // ICSj(F2), or, if not that,
>> if (HasBetterConversion)
>> return true;
>> + if (HasWorseConversion)
>> + return false;
>>
>> // -- the context is an initialization by user-defined conversion
>> // (see 8.5, 13.3.1.5) and the standard conversion sequence
>> @@ -9330,8 +9435,10 @@ bool clang::isBetterOverloadCandidate(
>> return BetterTemplate == Cand1.Function->getPrimaryTemplate();
>> }
>>
>> - // FIXME: Work around a defect in the C++17 inheriting constructor
>> wording.
>> - // A derived-class constructor beats an (inherited) base class
>> constructor.
>> + // -- F1 is a constructor for a class D, F2 is a constructor for a
>> base
>> + // class B of D, and for all arguments the corresponding
>> parameters of
>> + // F1 and F2 have the same type.
>> + // FIXME: Implement the "all parameters have the same type" check.
>> bool Cand1IsInherited =
>>
>> dyn_cast_or_null<ConstructorUsingShadowDecl>(Cand1.FoundDecl.getDecl());
>> bool Cand2IsInherited =
>> @@ -9349,6 +9456,16 @@ bool clang::isBetterOverloadCandidate(
>> // Inherited from sibling base classes: still ambiguous.
>> }
>>
>> + // -- F2 is a rewritten candidate (12.4.1.2) and F1 is not
>> + // -- F1 and F2 are rewritten candidates, and F2 is a synthesized
>> candidate
>> + // with reversed order of parameters and F1 is not
>> + //
>> + // We rank reversed + different operator as worse than just reversed,
>> but
>> + // that comparison can never happen, because we only consider
>> reversing for
>> + // the maximally-rewritten operator (== or <=>).
>> + if (Cand1.RewriteKind != Cand2.RewriteKind)
>> + return Cand1.RewriteKind < Cand2.RewriteKind;
>> +
>> // Check C++17 tie-breakers for deduction guides.
>> {
>> auto *Guide1 =
>> dyn_cast_or_null<CXXDeductionGuideDecl>(Cand1.Function);
>> @@ -9544,6 +9661,7 @@ namespace {
>> enum OverloadCandidateKind {
>> oc_function,
>> oc_method,
>> + oc_reversed_binary_operator,
>> oc_constructor,
>> oc_implicit_default_constructor,
>> oc_implicit_copy_constructor,
>> @@ -9561,6 +9679,7 @@ enum OverloadCandidateSelect {
>>
>> static std::pair<OverloadCandidateKind, OverloadCandidateSelect>
>> ClassifyOverloadCandidate(Sema &S, NamedDecl *Found, FunctionDecl *Fn,
>> + OverloadCandidateRewriteKind CRK,
>> std::string &Description) {
>>
>> bool isTemplate = Fn->isTemplateDecl() || Found->isTemplateDecl();
>> @@ -9577,6 +9696,9 @@ ClassifyOverloadCandidate(Sema &S, Named
>> }();
>>
>> OverloadCandidateKind Kind = [&]() {
>> + if (CRK & CRK_Reversed)
>> + return oc_reversed_binary_operator;
>> +
>> if (CXXConstructorDecl *Ctor = dyn_cast<CXXConstructorDecl>(Fn)) {
>> if (!Ctor->isImplicit()) {
>> if (isa<ConstructorUsingShadowDecl>(Found))
>> @@ -9701,6 +9823,7 @@ bool Sema::checkAddressOfFunctionIsAvail
>>
>> // Notes the location of an overload candidate.
>> void Sema::NoteOverloadCandidate(NamedDecl *Found, FunctionDecl *Fn,
>> + OverloadCandidateRewriteKind
>> RewriteKind,
>> QualType DestType, bool TakingAddress) {
>> if (TakingAddress && !checkAddressOfCandidateIsAvailable(*this, Fn))
>> return;
>> @@ -9710,7 +9833,7 @@ void Sema::NoteOverloadCandidate(NamedDe
>>
>> std::string FnDesc;
>> std::pair<OverloadCandidateKind, OverloadCandidateSelect> KSPair =
>> - ClassifyOverloadCandidate(*this, Found, Fn, FnDesc);
>> + ClassifyOverloadCandidate(*this, Found, Fn, RewriteKind, FnDesc);
>> PartialDiagnostic PD = PDiag(diag::note_ovl_candidate)
>> << (unsigned)KSPair.first <<
>> (unsigned)KSPair.second
>> << Fn << FnDesc;
>> @@ -9734,11 +9857,11 @@ void Sema::NoteAllOverloadCandidates(Exp
>> I != IEnd; ++I) {
>> if (FunctionTemplateDecl *FunTmpl =
>>
>> dyn_cast<FunctionTemplateDecl>((*I)->getUnderlyingDecl()) ) {
>> - NoteOverloadCandidate(*I, FunTmpl->getTemplatedDecl(), DestType,
>> + NoteOverloadCandidate(*I, FunTmpl->getTemplatedDecl(), CRK_None,
>> DestType,
>> TakingAddress);
>> } else if (FunctionDecl *Fun
>> =
>> dyn_cast<FunctionDecl>((*I)->getUnderlyingDecl()) ) {
>> - NoteOverloadCandidate(*I, Fun, DestType, TakingAddress);
>> + NoteOverloadCandidate(*I, Fun, CRK_None, DestType, TakingAddress);
>> }
>> }
>> }
>> @@ -9788,7 +9911,8 @@ static void DiagnoseBadConversion(Sema &
>>
>> std::string FnDesc;
>> std::pair<OverloadCandidateKind, OverloadCandidateSelect> FnKindPair =
>> - ClassifyOverloadCandidate(S, Cand->FoundDecl, Fn, FnDesc);
>> + ClassifyOverloadCandidate(S, Cand->FoundDecl, Fn,
>> Cand->RewriteKind,
>> + FnDesc);
>>
>> Expr *FromExpr = Conv.Bad.FromExpr;
>> QualType FromTy = Conv.Bad.getFromType();
>> @@ -10060,7 +10184,7 @@ static void DiagnoseArityMismatch(Sema &
>>
>> std::string Description;
>> std::pair<OverloadCandidateKind, OverloadCandidateSelect> FnKindPair =
>> - ClassifyOverloadCandidate(S, Found, Fn, Description);
>> + ClassifyOverloadCandidate(S, Found, Fn, CRK_None, Description);
>>
>> if (modeCount == 1 && Fn->getParamDecl(0)->getDeclName())
>> S.Diag(Fn->getLocation(), diag::note_ovl_candidate_arity_one)
>> @@ -10357,7 +10481,8 @@ static void DiagnoseBadTarget(Sema &S, O
>>
>> std::string FnDesc;
>> std::pair<OverloadCandidateKind, OverloadCandidateSelect> FnKindPair =
>> - ClassifyOverloadCandidate(S, Cand->FoundDecl, Callee, FnDesc);
>> + ClassifyOverloadCandidate(S, Cand->FoundDecl, Callee,
>> Cand->RewriteKind,
>> + FnDesc);
>>
>> S.Diag(Callee->getLocation(), diag::note_ovl_candidate_bad_target)
>> << (unsigned)FnKindPair.first << (unsigned)ocs_non_template
>> @@ -10475,7 +10600,8 @@ static void NoteFunctionCandidate(Sema &
>> if (Fn->isDeleted()) {
>> std::string FnDesc;
>> std::pair<OverloadCandidateKind, OverloadCandidateSelect>
>> FnKindPair =
>> - ClassifyOverloadCandidate(S, Cand->FoundDecl, Fn, FnDesc);
>> + ClassifyOverloadCandidate(S, Cand->FoundDecl, Fn,
>> Cand->RewriteKind,
>> + FnDesc);
>>
>> S.Diag(Fn->getLocation(), diag::note_ovl_candidate_deleted)
>> << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second
>> << FnDesc
>> @@ -10485,7 +10611,7 @@ static void NoteFunctionCandidate(Sema &
>> }
>>
>> // We don't really have anything else to say about viable candidates.
>> - S.NoteOverloadCandidate(Cand->FoundDecl, Fn);
>> + S.NoteOverloadCandidate(Cand->FoundDecl, Fn, Cand->RewriteKind);
>> return;
>> }
>>
>> @@ -10518,7 +10644,7 @@ static void NoteFunctionCandidate(Sema &
>> case ovl_fail_trivial_conversion:
>> case ovl_fail_bad_final_conversion:
>> case ovl_fail_final_conversion_not_exact:
>> - return S.NoteOverloadCandidate(Cand->FoundDecl, Fn);
>> + return S.NoteOverloadCandidate(Cand->FoundDecl, Fn,
>> Cand->RewriteKind);
>>
>> case ovl_fail_bad_conversion: {
>> unsigned I = (Cand->IgnoreObjectArgument ? 1 : 0);
>> @@ -10529,7 +10655,7 @@ static void NoteFunctionCandidate(Sema &
>> // FIXME: this currently happens when we're called from SemaInit
>> // when user-conversion overload fails. Figure out how to handle
>> // those conditions and diagnose them well.
>> - return S.NoteOverloadCandidate(Cand->FoundDecl, Fn);
>> + return S.NoteOverloadCandidate(Cand->FoundDecl, Fn,
>> Cand->RewriteKind);
>> }
>>
>> case ovl_fail_bad_target:
>> @@ -10805,8 +10931,10 @@ struct CompareOverloadCandidatesForDispl
>> /// CompleteNonViableCandidate - Normally, overload resolution only
>> /// computes up to the first bad conversion. Produces the FixIt set if
>> /// possible.
>> -static void CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
>> - ArrayRef<Expr *> Args) {
>> +static void
>> +CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
>> + ArrayRef<Expr *> Args,
>> + OverloadCandidateSet::CandidateSetKind CSK) {
>> assert(!Cand->Viable);
>>
>> // Don't do anything on failures other than bad conversion.
>> @@ -10834,6 +10962,7 @@ static void CompleteNonViableCandidate(S
>> bool SuppressUserConversions = false;
>>
>> unsigned ConvIdx = 0;
>> + unsigned ArgIdx = 0;
>> ArrayRef<QualType> ParamTypes;
>>
>> if (Cand->IsSurrogate) {
>> @@ -10842,15 +10971,18 @@ static void CompleteNonViableCandidate(S
>> if (const PointerType *ConvPtrType = ConvType->getAs<PointerType>())
>> ConvType = ConvPtrType->getPointeeType();
>> ParamTypes = ConvType->castAs<FunctionProtoType>()->getParamTypes();
>> - // Conversion 0 is 'this', which doesn't have a corresponding
>> argument.
>> + // Conversion 0 is 'this', which doesn't have a corresponding
>> parameter.
>> ConvIdx = 1;
>> } else if (Cand->Function) {
>> ParamTypes =
>>
>> Cand->Function->getType()->castAs<FunctionProtoType>()->getParamTypes();
>> if (isa<CXXMethodDecl>(Cand->Function) &&
>> !isa<CXXConstructorDecl>(Cand->Function)) {
>> - // Conversion 0 is 'this', which doesn't have a corresponding
>> argument.
>> + // Conversion 0 is 'this', which doesn't have a corresponding
>> parameter.
>> ConvIdx = 1;
>> + if (CSK == OverloadCandidateSet::CSK_Operator)
>> + // Argument 0 is 'this', which doesn't have a corresponding
>> parameter.
>> + ArgIdx = 1;
>> }
>> } else {
>> // Builtin operator.
>> @@ -10859,16 +10991,19 @@ static void CompleteNonViableCandidate(S
>> }
>>
>> // Fill in the rest of the conversions.
>> - for (unsigned ArgIdx = 0; ConvIdx != ConvCount; ++ConvIdx, ++ArgIdx) {
>> + bool Reversed = Cand->RewriteKind & CRK_Reversed;
>> + for (unsigned ParamIdx = Reversed ? ParamTypes.size() - 1 : 0;
>> + ConvIdx != ConvCount;
>> + ++ConvIdx, ++ArgIdx, ParamIdx += (Reversed ? -1 : 1)) {
>> if (Cand->Conversions[ConvIdx].isInitialized()) {
>> // We've already checked this conversion.
>> } else if (ArgIdx < ParamTypes.size()) {
>> - if (ParamTypes[ArgIdx]->isDependentType())
>> + if (ParamTypes[ParamIdx]->isDependentType())
>> Cand->Conversions[ConvIdx].setAsIdentityConversion(
>> Args[ArgIdx]->getType());
>> else {
>> Cand->Conversions[ConvIdx] =
>> - TryCopyInitialization(S, Args[ArgIdx], ParamTypes[ArgIdx],
>> + TryCopyInitialization(S, Args[ArgIdx], ParamTypes[ParamIdx],
>> SuppressUserConversions,
>> /*InOverloadResolution=*/true,
>> /*AllowObjCWritebackConversion=*/
>> @@ -10896,7 +11031,7 @@ SmallVector<OverloadCandidate *, 32> Ove
>> if (Cand->Viable)
>> Cands.push_back(Cand);
>> else if (OCD == OCD_AllCandidates) {
>> - CompleteNonViableCandidate(S, Cand, Args);
>> + CompleteNonViableCandidate(S, Cand, Args, Kind);
>> if (Cand->Function || Cand->IsSurrogate)
>> Cands.push_back(Cand);
>> // Otherwise, this a non-viable builtin candidate. We do not, in
>> general,
>> @@ -11453,7 +11588,7 @@ public:
>> if (FunctionDecl *Fun =
>> dyn_cast<FunctionDecl>((*I)->getUnderlyingDecl()))
>> if (!functionHasPassObjectSizeParams(Fun))
>> - S.NoteOverloadCandidate(*I, Fun, TargetFunctionType,
>> + S.NoteOverloadCandidate(*I, Fun, CRK_None,
>> TargetFunctionType,
>> /*TakingAddress=*/true);
>> FailedCandidates.NoteCandidates(S, OvlExpr->getBeginLoc());
>> }
>> @@ -12403,7 +12538,7 @@ Sema::CreateOverloadedUnaryOp(SourceLoca
>> OverloadCandidateSet CandidateSet(OpLoc,
>> OverloadCandidateSet::CSK_Operator);
>>
>> // Add the candidates from the given function set.
>> - AddFunctionCandidates(Fns, ArgsArray, CandidateSet);
>> + AddNonMemberOperatorCandidates(Fns, ArgsArray, CandidateSet);
>>
>> // Add operator candidates that are member functions.
>> AddMemberOperatorCandidates(Op, OpLoc, ArgsArray, CandidateSet);
>> @@ -12548,14 +12683,17 @@ Sema::CreateOverloadedUnaryOp(SourceLoca
>> ///
>> /// \param LHS Left-hand argument.
>> /// \param RHS Right-hand argument.
>> -ExprResult
>> -Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
>> - BinaryOperatorKind Opc,
>> - const UnresolvedSetImpl &Fns,
>> - Expr *LHS, Expr *RHS, bool PerformADL) {
>> +ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
>> + BinaryOperatorKind Opc,
>> + const UnresolvedSetImpl &Fns,
>> Expr *LHS,
>> + Expr *RHS, bool PerformADL,
>> + bool AllowRewrittenCandidates) {
>> Expr *Args[2] = { LHS, RHS };
>> LHS=RHS=nullptr; // Please use only Args instead of LHS/RHS couple
>>
>> + if (!getLangOpts().CPlusPlus2a)
>> + AllowRewrittenCandidates = false;
>> +
>> OverloadedOperatorKind Op = BinaryOperator::getOverloadedOperator(Opc);
>> DeclarationName OpName =
>> Context.DeclarationNames.getCXXOperatorName(Op);
>>
>> @@ -12613,23 +12751,61 @@ Sema::CreateOverloadedBinOp(SourceLocati
>> return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
>>
>> // Build an empty overload set.
>> - OverloadCandidateSet CandidateSet(OpLoc,
>> OverloadCandidateSet::CSK_Operator);
>> + OverloadCandidateSet CandidateSet(
>> + OpLoc, OverloadCandidateSet::CSK_Operator,
>> + OverloadCandidateSet::OperatorRewriteInfo(Op,
>> AllowRewrittenCandidates));
>>
>> - // Add the candidates from the given function set.
>> - AddFunctionCandidates(Fns, Args, CandidateSet);
>> + OverloadedOperatorKind ExtraOp =
>> + AllowRewrittenCandidates ? getRewrittenOverloadedOperator(Op) :
>> OO_None;
>> +
>> + // Add the candidates from the given function set. This also adds the
>> + // rewritten candidates using these functions if necessary.
>> + AddNonMemberOperatorCandidates(Fns, Args, CandidateSet);
>>
>> // Add operator candidates that are member functions.
>> AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
>> + if (CandidateSet.getRewriteInfo().shouldAddReversed(Op))
>> + AddMemberOperatorCandidates(Op, OpLoc, {Args[1], Args[0]},
>> CandidateSet,
>> + OverloadCandidateParamOrder::Reversed);
>> +
>> + // In C++20, also add any rewritten member candidates.
>> + if (ExtraOp) {
>> + AddMemberOperatorCandidates(ExtraOp, OpLoc, Args, CandidateSet);
>> + if (CandidateSet.getRewriteInfo().shouldAddReversed(ExtraOp))
>> + AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
>> + CandidateSet,
>> + OverloadCandidateParamOrder::Reversed);
>> + }
>>
>> // Add candidates from ADL. Per [over.match.oper]p2, this lookup is not
>> // performed for an assignment operator (nor for operator[] nor
>> operator->,
>> // which don't get here).
>> - if (Opc != BO_Assign && PerformADL)
>> + if (Opc != BO_Assign && PerformADL) {
>> AddArgumentDependentLookupCandidates(OpName, OpLoc, Args,
>> /*ExplicitTemplateArgs*/
>> nullptr,
>> CandidateSet);
>> + if (ExtraOp) {
>> + DeclarationName ExtraOpName =
>> + Context.DeclarationNames.getCXXOperatorName(ExtraOp);
>> + AddArgumentDependentLookupCandidates(ExtraOpName, OpLoc, Args,
>> + /*ExplicitTemplateArgs*/
>> nullptr,
>> + CandidateSet);
>> + }
>> + }
>>
>> // Add builtin operator candidates.
>> + //
>> + // FIXME: We don't add any rewritten candidates here. This is strictly
>> + // incorrect; a builtin candidate could be hidden by a non-viable
>> candidate,
>> + // resulting in our selecting a rewritten builtin candidate. For
>> example:
>> + //
>> + // enum class E { e };
>> + // bool operator!=(E, E) requires false;
>> + // bool k = E::e != E::e;
>> + //
>> + // ... should select the rewritten builtin candidate 'operator==(E,
>> E)'. But
>> + // it seems unreasonable to consider rewritten builtin candidates. A
>> core
>> + // issue has been filed proposing to removed this requirement.
>> AddBuiltinOperatorCandidates(Op, OpLoc, Args, CandidateSet);
>>
>> bool HadMultipleCandidates = (CandidateSet.size() > 1);
>> @@ -12641,11 +12817,57 @@ Sema::CreateOverloadedBinOp(SourceLocati
>> // We found a built-in operator or an overloaded operator.
>> FunctionDecl *FnDecl = Best->Function;
>>
>> + bool IsReversed = (Best->RewriteKind & CRK_Reversed);
>> + if (IsReversed)
>> + std::swap(Args[0], Args[1]);
>> +
>> if (FnDecl) {
>> Expr *Base = nullptr;
>> // We matched an overloaded operator. Build a call to that
>> // operator.
>>
>> + OverloadedOperatorKind ChosenOp =
>> + FnDecl->getDeclName().getCXXOverloadedOperator();
>> +
>> + // C++2a [over.match.oper]p9:
>> + // If a rewritten operator== candidate is selected by overload
>> + // resolution for an operator@, its return type shall be cv
>> bool
>> + if (Best->RewriteKind && ChosenOp == OO_EqualEqual &&
>> + !FnDecl->getReturnType()->isBooleanType()) {
>> + Diag(OpLoc, diag::err_ovl_rewrite_equalequal_not_bool)
>> + << FnDecl->getReturnType() <<
>> BinaryOperator::getOpcodeStr(Opc)
>> + << Args[0]->getSourceRange() << Args[1]->getSourceRange();
>> + Diag(FnDecl->getLocation(), diag::note_declared_at);
>> + return ExprError();
>> + }
>> +
>> + if (AllowRewrittenCandidates && !IsReversed &&
>> + CandidateSet.getRewriteInfo().shouldAddReversed(ChosenOp)) {
>> + // We could have reversed this operator, but didn't. Check if
>> the
>> + // reversed form was a viable candidate, and if so, if it had a
>> + // better conversion for either parameter. If so, this call is
>> + // formally ambiguous, and allowing it is an extension.
>> + for (OverloadCandidate &Cand : CandidateSet) {
>> + if (Cand.Viable && Cand.Function == FnDecl &&
>> + Cand.RewriteKind & CRK_Reversed) {
>> + for (unsigned ArgIdx = 0; ArgIdx < 2; ++ArgIdx) {
>> + if (CompareImplicitConversionSequences(
>> + *this, OpLoc, Cand.Conversions[ArgIdx],
>> + Best->Conversions[ArgIdx]) ==
>> + ImplicitConversionSequence::Better) {
>> + Diag(OpLoc,
>> diag::ext_ovl_ambiguous_oper_binary_reversed)
>> + << BinaryOperator::getOpcodeStr(Opc)
>> + << Args[0]->getType() << Args[1]->getType()
>> + << Args[0]->getSourceRange() <<
>> Args[1]->getSourceRange();
>> + Diag(FnDecl->getLocation(),
>> +
>> diag::note_ovl_ambiguous_oper_binary_reversed_candidate);
>> + }
>> + }
>> + break;
>> + }
>> + }
>> + }
>> +
>> // Convert the arguments.
>> if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) {
>> // Best->Access is only meaningful for class members.
>> @@ -12699,8 +12921,8 @@ Sema::CreateOverloadedBinOp(SourceLocati
>> ResultTy = ResultTy.getNonLValueExprType(Context);
>>
>> CXXOperatorCallExpr *TheCall = CXXOperatorCallExpr::Create(
>> - Context, Op, FnExpr.get(), Args, ResultTy, VK, OpLoc,
>> FPFeatures,
>> - Best->IsADLCandidate);
>> + Context, ChosenOp, FnExpr.get(), Args, ResultTy, VK, OpLoc,
>> + FPFeatures, Best->IsADLCandidate);
>>
>> if (CheckCallReturnType(FnDecl->getReturnType(), OpLoc, TheCall,
>> FnDecl))
>> @@ -12722,7 +12944,46 @@ Sema::CreateOverloadedBinOp(SourceLocati
>> isa<CXXMethodDecl>(FnDecl), OpLoc,
>> TheCall->getSourceRange(),
>> VariadicDoesNotApply);
>>
>> - return MaybeBindToTemporary(TheCall);
>> + ExprResult R = MaybeBindToTemporary(TheCall);
>> + if (R.isInvalid())
>> + return ExprError();
>> +
>> + // For a rewritten candidate, we've already reversed the
>> arguments
>> + // if needed. Perform the rest of the rewrite now.
>> + if ((Best->RewriteKind & CRK_DifferentOperator) ||
>> + (Op == OO_Spaceship && IsReversed)) {
>> + if (Op == OO_ExclaimEqual) {
>> + assert(ChosenOp == OO_EqualEqual && "unexpected operator
>> name");
>> + R = CreateBuiltinUnaryOp(OpLoc, UO_LNot, R.get());
>> + } else {
>> + assert(ChosenOp == OO_Spaceship && "unexpected operator
>> name");
>> + llvm::APSInt Zero(Context.getTypeSize(Context.IntTy), false);
>> + Expr *ZeroLiteral =
>> + IntegerLiteral::Create(Context, Zero, Context.IntTy,
>> OpLoc);
>> +
>> + Sema::CodeSynthesisContext Ctx;
>> + Ctx.Kind =
>> Sema::CodeSynthesisContext::RewritingOperatorAsSpaceship;
>> + Ctx.Entity = FnDecl;
>> + pushCodeSynthesisContext(Ctx);
>> +
>> + R = CreateOverloadedBinOp(
>> + OpLoc, Opc, Fns, IsReversed ? ZeroLiteral : R.get(),
>> + IsReversed ? R.get() : ZeroLiteral, PerformADL,
>> + /*AllowRewrittenCandidates=*/false);
>> +
>> + popCodeSynthesisContext();
>> + }
>> + if (R.isInvalid())
>> + return ExprError();
>> + } else {
>> + assert(ChosenOp == Op && "unexpected operator name");
>> + }
>> +
>> + // Make a note in the AST if we did any rewriting.
>> + if (Best->RewriteKind != CRK_None)
>> + R = new (Context) CXXRewrittenBinaryOperator(R.get(),
>> IsReversed);
>> +
>> + return R;
>> } else {
>> // We matched a built-in operator. Convert the arguments, then
>> // break out so that we will build the appropriate built-in
>> @@ -12812,10 +13073,12 @@ Sema::CreateOverloadedBinOp(SourceLocati
>> return ExprError();
>> }
>> CandidateSet.NoteCandidates(
>> - PartialDiagnosticAt(OpLoc, PDiag(diag::err_ovl_deleted_oper)
>> - <<
>> BinaryOperator::getOpcodeStr(Opc)
>> - << Args[0]->getSourceRange()
>> - << Args[1]->getSourceRange()),
>> + PartialDiagnosticAt(
>> + OpLoc, PDiag(diag::err_ovl_deleted_oper)
>> + <<
>> getOperatorSpelling(Best->Function->getDeclName()
>> +
>> .getCXXOverloadedOperator())
>> + << Args[0]->getSourceRange()
>> + << Args[1]->getSourceRange()),
>> *this, OCD_AllCandidates, Args,
>> BinaryOperator::getOpcodeStr(Opc),
>> OpLoc);
>> return ExprError();
>> @@ -13692,8 +13955,8 @@ ExprResult Sema::BuildLiteralOperatorCal
>>
>> OverloadCandidateSet CandidateSet(UDSuffixLoc,
>> OverloadCandidateSet::CSK_Normal);
>> - AddFunctionCandidates(R.asUnresolvedSet(), Args, CandidateSet,
>> TemplateArgs,
>> - /*SuppressUserConversions=*/true);
>> + AddNonMemberOperatorCandidates(R.asUnresolvedSet(), Args, CandidateSet,
>> + TemplateArgs);
>>
>> bool HadMultipleCandidates = (CandidateSet.size() > 1);
>>
>>
>> Modified: cfe/trunk/lib/Sema/SemaTemplate.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaTemplate.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/Sema/SemaTemplate.cpp (original)
>> +++ cfe/trunk/lib/Sema/SemaTemplate.cpp Fri Oct 18 17:04:43 2019
>> @@ -24,6 +24,7 @@
>> #include "clang/Basic/TargetInfo.h"
>> #include "clang/Sema/DeclSpec.h"
>> #include "clang/Sema/Lookup.h"
>> +#include "clang/Sema/Overload.h"
>> #include "clang/Sema/ParsedTemplate.h"
>> #include "clang/Sema/Scope.h"
>> #include "clang/Sema/SemaInternal.h"
>> @@ -8488,7 +8489,7 @@ bool Sema::CheckFunctionTemplateSpeciali
>> // candidates at once, to get proper sorting and limiting.
>> for (auto *OldND : Previous) {
>> if (auto *OldFD =
>> dyn_cast<FunctionDecl>(OldND->getUnderlyingDecl()))
>> - NoteOverloadCandidate(OldND, OldFD, FD->getType(), false);
>> + NoteOverloadCandidate(OldND, OldFD, CRK_None, FD->getType(),
>> false);
>> }
>> FailedCandidates.NoteCandidates(*this, FD->getLocation());
>> return true;
>>
>> Modified: cfe/trunk/lib/Sema/SemaTemplateInstantiate.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaTemplateInstantiate.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/Sema/SemaTemplateInstantiate.cpp (original)
>> +++ cfe/trunk/lib/Sema/SemaTemplateInstantiate.cpp Fri Oct 18 17:04:43
>> 2019
>> @@ -206,6 +206,7 @@ bool Sema::CodeSynthesisContext::isInsta
>> case DefiningSynthesizedFunction:
>> case ExceptionSpecEvaluation:
>> case ConstraintSubstitution:
>> + case RewritingOperatorAsSpaceship:
>> return false;
>>
>> // This function should never be called when Kind's value is
>> Memoization.
>> @@ -682,6 +683,11 @@ void Sema::PrintInstantiationStack() {
>> break;
>> }
>>
>> + case CodeSynthesisContext::RewritingOperatorAsSpaceship:
>> + Diags.Report(Active->Entity->getLocation(),
>> + diag::note_rewriting_operator_as_spaceship);
>> + break;
>> +
>> case CodeSynthesisContext::Memoization:
>> break;
>>
>> @@ -754,6 +760,7 @@ Optional<TemplateDeductionInfo *> Sema::
>>
>> case CodeSynthesisContext::DeclaringSpecialMember:
>> case CodeSynthesisContext::DefiningSynthesizedFunction:
>> + case CodeSynthesisContext::RewritingOperatorAsSpaceship:
>> // This happens in a context unrelated to template instantiation,
>> so
>> // there is no SFINAE.
>> return None;
>>
>> Modified: cfe/trunk/lib/Sema/TreeTransform.h
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/TreeTransform.h?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/Sema/TreeTransform.h (original)
>> +++ cfe/trunk/lib/Sema/TreeTransform.h Fri Oct 18 17:04:43 2019
>> @@ -2355,13 +2355,13 @@ public:
>>
>> /// Build a new rewritten operator expression.
>> ///
>> - /// By default, builds the rewritten operator without performing any
>> semantic
>> - /// analysis. Subclasses may override this routine to provide different
>> - /// behavior.
>> - ExprResult RebuildCXXRewrittenBinaryOperator(Expr *SemanticForm,
>> - bool IsReversed) {
>> - return new (getSema().Context)
>> - CXXRewrittenBinaryOperator(SemanticForm, IsReversed);
>> + /// By default, performs semantic analysis to build the new expression.
>> + /// Subclasses may override this routine to provide different behavior.
>> + ExprResult RebuildCXXRewrittenBinaryOperator(
>> + SourceLocation OpLoc, BinaryOperatorKind Opcode,
>> + const UnresolvedSetImpl &UnqualLookups, Expr *LHS, Expr *RHS) {
>> + return getSema().CreateOverloadedBinOp(OpLoc, Opcode, UnqualLookups,
>> LHS,
>> + RHS, /*RequiresADL*/false);
>> }
>>
>> /// Build a new conditional operator expression.
>> @@ -9783,24 +9783,45 @@ TreeTransform<Derived>::TransformBinaryO
>> template <typename Derived>
>> ExprResult TreeTransform<Derived>::TransformCXXRewrittenBinaryOperator(
>> CXXRewrittenBinaryOperator *E) {
>> - // FIXME: C++ [temp.deduct]p7 "The substitution proceeds in lexical
>> order and
>> - // stops when a condition that causes deduction to fail is
>> encountered."
>> - // requires us to substitute into the LHS before the RHS, even in a
>> rewrite
>> - // that reversed the operand order.
>> - //
>> - // We can't decompose back to a binary operator here, because that
>> would lose
>> - // the unqualified lookup results from the phase 1 name lookup.
>> + CXXRewrittenBinaryOperator::DecomposedForm Decomp =
>> E->getDecomposedForm();
>>
>> - ExprResult SemanticForm =
>> getDerived().TransformExpr(E->getSemanticForm());
>> - if (SemanticForm.isInvalid())
>> + ExprResult LHS =
>> getDerived().TransformExpr(const_cast<Expr*>(Decomp.LHS));
>> + if (LHS.isInvalid())
>> + return ExprError();
>> +
>> + ExprResult RHS =
>> getDerived().TransformExpr(const_cast<Expr*>(Decomp.RHS));
>> + if (RHS.isInvalid())
>> return ExprError();
>>
>> if (!getDerived().AlwaysRebuild() &&
>> - SemanticForm.get() == E->getSemanticForm())
>> + LHS.get() == Decomp.LHS &&
>> + RHS.get() == Decomp.RHS)
>> return E;
>>
>> - return
>> getDerived().RebuildCXXRewrittenBinaryOperator(SemanticForm.get(),
>> - E->isReversed());
>> + // Extract the already-resolved callee declarations so that we can
>> restrict
>> + // ourselves to using them as the unqualified lookup results when
>> rebuilding.
>> + UnresolvedSet<2> UnqualLookups;
>> + Expr *PossibleBinOps[] = {E->getSemanticForm(),
>> + const_cast<Expr *>(Decomp.InnerBinOp)};
>> + for (Expr *PossibleBinOp : PossibleBinOps) {
>> + auto *Op =
>> dyn_cast<CXXOperatorCallExpr>(PossibleBinOp->IgnoreImplicit());
>> + if (!Op)
>> + continue;
>> + auto *Callee =
>> dyn_cast<DeclRefExpr>(Op->getCallee()->IgnoreImplicit());
>> + if (!Callee || isa<CXXMethodDecl>(Callee->getDecl()))
>> + continue;
>> +
>> + // Transform the callee in case we built a call to a local extern
>> + // declaration.
>> + NamedDecl *Found =
>> cast_or_null<NamedDecl>(getDerived().TransformDecl(
>> + E->getOperatorLoc(), Callee->getFoundDecl()));
>> + if (!Found)
>> + return ExprError();
>> + UnqualLookups.addDecl(Found);
>> + }
>> +
>> + return getDerived().RebuildCXXRewrittenBinaryOperator(
>> + E->getOperatorLoc(), Decomp.Opcode, UnqualLookups, LHS.get(),
>> RHS.get());
>> }
>>
>> template<typename Derived>
>>
>> Added:
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp?rev=375306&view=auto
>>
>> ==============================================================================
>> ---
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp
>> (added)
>> +++
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp
>> Fri Oct 18 17:04:43 2019
>> @@ -0,0 +1,172 @@
>> +// RUN: %clang_cc1 -std=c++2a -verify %s
>> +// RUN: %clang_cc1 -std=c++2a -verify -Wall -DNO_ERRORS %s
>> +
>> +#ifndef NO_ERRORS
>> +namespace bullet3 {
>> + // the built-in candidates include all of the candidate operator
>> fnuctions
>> + // [...] that, compared to the given operator
>> +
>> + // - do not have the same parameter-type-list as any non-member
>> candidate
>> +
>> + enum E { e };
>> +
>> + // Suppress both builtin operator<=>(E, E) and operator<(E, E).
>> + void operator<=>(E, E); // expected-note {{while rewriting}}
>> + bool cmp = e < e; // expected-error {{invalid operands to binary
>> expression ('void' and 'int')}}
>> +
>> + // None of the other bullets have anything to test here. In principle
>> we
>> + // need to suppress both builtin operator@(A, B) and operator@(B, A)
>> when we
>> + // see a user-declared reversible operator@(A, B), and we do, but
>> that's
>> + // untestable because the only built-in reversible candidates are
>> + // operator<=>(E, E) and operator==(E, E) for E an enumeration type,
>> and
>> + // those are both symmetric anyway.
>> +}
>> +
>> +namespace bullet4 {
>> + // The rewritten candidate set is determined as follows:
>> +
>> + template<int> struct X {};
>> + X<1> x1;
>> + X<2> x2;
>> +
>> + struct Y {
>> + int operator<=>(X<2>) = delete; // #1member
>> + bool operator==(X<2>) = delete; // #2member
>> + };
>> + Y y;
>> +
>> + // - For the relational operators, the rewritten candidates include all
>> + // non-rewritten candidates for the expression x <=> y.
>> + int operator<=>(X<1>, X<2>) = delete; // #1
>> +
>> + // expected-note@#1 5{{candidate function has been explicitly
>> deleted}}
>> + // expected-note@#1 5{{candidate function (with reversed parameter
>> order) not viable: no known conversion from 'X<1>' to 'X<2>' for 1st
>> argument}}
>> + bool lt = x1 < x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool le = x1 <= x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool gt = x1 > x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool ge = x1 >= x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool cmp = x1 <=> x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> +
>> + // expected-note@#1member 5{{candidate function has been explicitly
>> deleted}}
>> + // expected-note@#1 5{{candidate function not viable: no known
>> conversion from 'bullet4::Y' to 'X<1>' for 1st argument}}
>> + // expected-note@#1 5{{candidate function (with reversed parameter
>> order) not viable: no known conversion from 'bullet4::Y' to 'X<2>' for 1st
>> argument}}
>> + bool mem_lt = y < x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_le = y <= x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_gt = y > x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_ge = y >= x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_cmp = y <=> x2; // expected-error {{selected deleted operator
>> '<=>'}}
>> +
>> + // - For the relational and three-way comparison operators, the
>> rewritten
>> + // candidates also include a synthesized candidate, with the order
>> of the
>> + // two parameters reversed, for each non-rewritten candidate for the
>> + // expression y <=> x.
>> +
>> + // expected-note@#1 5{{candidate function (with reversed parameter
>> order) has been explicitly deleted}}
>> + // expected-note@#1 5{{candidate function not viable: no known
>> conversion from 'X<2>' to 'X<1>' for 1st argument}}
>> + bool rlt = x2 < x1; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool rle = x2 <= x1; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool rgt = x2 > x1; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool rge = x2 >= x1; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool rcmp = x2 <=> x1; // expected-error {{selected deleted operator
>> '<=>'}}
>> +
>> + // expected-note@#1member 5{{candidate function (with reversed
>> parameter order) has been explicitly deleted}}
>> + // expected-note@#1 5{{candidate function not viable: no known
>> conversion from 'X<2>' to 'X<1>' for 1st argument}}
>> + // expected-note@#1 5{{candidate function (with reversed parameter
>> order) not viable: no known conversion from 'bullet4::Y' to 'X<1>' for 2nd
>> argument}}
>> + bool mem_rlt = x2 < y; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_rle = x2 <= y; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_rgt = x2 > y; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_rge = x2 >= y; // expected-error {{selected deleted operator
>> '<=>'}}
>> + bool mem_rcmp = x2 <=> y; // expected-error {{selected deleted
>> operator '<=>'}}
>> +
>> + // For the != operator, the rewritten candidates include all
>> non-rewritten
>> + // candidates for the expression x == y
>> + int operator==(X<1>, X<2>) = delete; // #2
>> +
>> + // expected-note@#2 2{{candidate function has been explicitly
>> deleted}}
>> + // expected-note@#2 2{{candidate function (with reversed parameter
>> order) not viable: no known conversion from 'X<1>' to 'X<2>' for 1st
>> argument}}
>> + bool eq = x1 == x2; // expected-error {{selected deleted operator
>> '=='}}
>> + bool ne = x1 != x2; // expected-error {{selected deleted operator
>> '=='}}
>> +
>> + // expected-note@#2member 2{{candidate function has been explicitly
>> deleted}}
>> + // expected-note@#2 2{{candidate function not viable: no known
>> conversion from 'bullet4::Y' to 'X<1>' for 1st argument}}
>> + // expected-note@#2 2{{candidate function (with reversed parameter
>> order) not viable: no known conversion from 'bullet4::Y' to 'X<2>' for 1st
>> argument}}
>> + bool mem_eq = y == x2; // expected-error {{selected deleted operator
>> '=='}}
>> + bool mem_ne = y != x2; // expected-error {{selected deleted operator
>> '=='}}
>> +
>> + // For the equality operators, the rewritten candidates also include a
>> + // synthesized candidate, with the order of the two parameters
>> reversed, for
>> + // each non-rewritten candidate for the expression y == x
>> +
>> + // expected-note@#2 2{{candidate function (with reversed parameter
>> order) has been explicitly deleted}}
>> + // expected-note@#2 2{{candidate function not viable: no known
>> conversion from 'X<2>' to 'X<1>' for 1st argument}}
>> + bool req = x2 == x1; // expected-error {{selected deleted operator
>> '=='}}
>> + bool rne = x2 != x1; // expected-error {{selected deleted operator
>> '=='}}
>> +
>> + // expected-note@#2member 2{{candidate function (with reversed
>> parameter order) has been explicitly deleted}}
>> + // expected-note@#2 2{{candidate function not viable: no known
>> conversion from 'X<2>' to 'X<1>' for 1st argument}}
>> + // expected-note@#2 2{{candidate function (with reversed parameter
>> order) not viable: no known conversion from 'bullet4::Y' to 'X<1>' for 2nd
>> argument}}
>> + bool mem_req = x2 == y; // expected-error {{selected deleted operator
>> '=='}}
>> + bool mem_rne = x2 != y; // expected-error {{selected deleted operator
>> '=='}}
>> +
>> + // For all other operators, the rewritten candidate set is empty.
>> + X<3> operator+(X<1>, X<2>) = delete; // expected-note {{no known
>> conversion from 'X<2>' to 'X<1>'}}
>> + X<3> reversed_add = x2 + x1; // expected-error {{invalid operands}}
>> +}
>> +
>> +// Various C++17 cases that are known to be broken by the C++20 rules.
>> +namespace problem_cases {
>> + // We can have an ambiguity between an operator and its reversed form.
>> This
>> + // wasn't intended by the original "consistent comparison" proposal,
>> and we
>> + // allow it as extension, picking the non-reversed form.
>> + struct A {
>> + bool operator==(const A&); // expected-note {{ambiguity is between a
>> regular call to this operator and a call with the argument order reversed}}
>> + };
>> + bool cmp_non_const = A() == A(); // expected-warning {{ambiguous}}
>> +
>> + struct B {
>> + virtual bool operator==(const B&) const;
>> + };
>> + struct D : B {
>> + bool operator==(const B&) const override; // expected-note
>> {{operator}}
>> + };
>> + bool cmp_base_derived = D() == D(); // expected-warning {{ambiguous}}
>> +
>> + template<typename T> struct CRTPBase {
>> + bool operator==(const T&) const; // expected-note {{operator}}
>> + };
>> + struct CRTP : CRTPBase<CRTP> {};
>> + bool cmp_crtp = CRTP() == CRTP(); // expected-warning {{ambiguous}}
>> +
>> + // We can select a non-rewriteable operator== for a != comparison,
>> when there
>> + // was a viable operator!= candidate we could have used instead.
>> + //
>> + // Rejecting this seems OK on balance.
>> + using UBool = signed char; // ICU uses this.
>> + struct ICUBase {
>> + virtual UBool operator==(const ICUBase&) const;
>> + UBool operator!=(const ICUBase &arg) const { return
>> !operator==(arg); }
>> + };
>> + struct ICUDerived : ICUBase {
>> + UBool operator==(const ICUBase&) const override; // expected-note
>> {{declared here}}
>> + };
>> + bool cmp_icu = ICUDerived() != ICUDerived(); // expected-error {{not
>> 'bool'}}
>> +}
>> +
>> +#else // NO_ERRORS
>> +
>> +namespace problem_cases {
>> + // We can select a reversed candidate where we used to select a
>> non-reversed
>> + // one, and in the worst case this can dramatically change the meaning
>> of the
>> + // program. Make sure we at least warn on the worst cases under -Wall.
>> + struct iterator;
>> + struct const_iterator {
>> + const_iterator(iterator);
>> + bool operator==(const const_iterator&) const;
>> + };
>> + struct iterator {
>> + bool operator==(const const_iterator &o) const { // expected-warning
>> {{all paths through this function will call itself}}
>> + return o == *this;
>> + }
>> + };
>> +}
>> +#endif // NO_ERRORS
>>
>> Added:
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp?rev=375306&view=auto
>>
>> ==============================================================================
>> ---
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp
>> (added)
>> +++
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp
>> Fri Oct 18 17:04:43 2019
>> @@ -0,0 +1,70 @@
>> +// RUN: %clang_cc1 -std=c++2a -verify %s
>> +
>> +template<typename L, typename R> struct Op { L l; const char *op; R r; };
>> +// FIXME: Remove once we implement P1816R0.
>> +template<typename L, typename R> Op(L, R) -> Op<L, R>;
>> +
>> +struct A {};
>> +struct B {};
>> +constexpr Op<A, B> operator<=>(A a, B b) { return {a, "<=>", b}; }
>> +
>> +template<typename T, typename U, typename V> constexpr Op<Op<T, U>, V>
>> operator< (Op<T, U> a, V b) { return {a, "<", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<Op<T, U>, V>
>> operator<= (Op<T, U> a, V b) { return {a, "<=", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<Op<T, U>, V>
>> operator> (Op<T, U> a, V b) { return {a, ">", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<Op<T, U>, V>
>> operator>= (Op<T, U> a, V b) { return {a, ">=", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<Op<T, U>, V>
>> operator<=>(Op<T, U> a, V b) { return {a, "<=>", b}; }
>> +
>> +template<typename T, typename U, typename V> constexpr Op<T, Op<U, V>>
>> operator< (T a, Op<U, V> b) { return {a, "<", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<T, Op<U, V>>
>> operator<= (T a, Op<U, V> b) { return {a, "<=", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<T, Op<U, V>>
>> operator> (T a, Op<U, V> b) { return {a, ">", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<T, Op<U, V>>
>> operator>= (T a, Op<U, V> b) { return {a, ">=", b}; }
>> +template<typename T, typename U, typename V> constexpr Op<T, Op<U, V>>
>> operator<=>(T a, Op<U, V> b) { return {a, "<=>", b}; }
>> +
>> +constexpr bool same(A, A) { return true; }
>> +constexpr bool same(B, B) { return true; }
>> +constexpr bool same(int a, int b) { return a == b; }
>> +template<typename T, typename U>
>> +constexpr bool same(Op<T, U> x, Op<T, U> y) {
>> + return same(x.l, y.l) && __builtin_strcmp(x.op, y.op) == 0 &&
>> same(x.r, y.r);
>> +}
>> +
>> +// x @ y is interpreted as:
>> +void f(A x, B y) {
>> + // -- (x <=> y) @ 0 if not reversed
>> + static_assert(same(x < y, (x <=> y) < 0));
>> + static_assert(same(x <= y, (x <=> y) <= 0));
>> + static_assert(same(x > y, (x <=> y) > 0));
>> + static_assert(same(x >= y, (x <=> y) >= 0));
>> + static_assert(same(x <=> y, x <=> y)); // (not rewritten)
>> +}
>> +
>> +void g(B x, A y) {
>> + // -- 0 @ (y <=> x) if reversed
>> + static_assert(same(x < y, 0 < (y <=> x)));
>> + static_assert(same(x <= y, 0 <= (y <=> x)));
>> + static_assert(same(x > y, 0 > (y <=> x)));
>> + static_assert(same(x >= y, 0 >= (y <=> x)));
>> + static_assert(same(x <=> y, 0 <=> (y <=> x)));
>> +}
>> +
>> +
>> +// We can rewrite into a call involving a builtin operator.
>> +struct X { int result; };
>> +struct Y {};
>> +constexpr int operator<=>(X x, Y) { return x.result; }
>> +static_assert(X{-1} < Y{});
>> +static_assert(X{0} < Y{}); // expected-error {{failed}}
>> +static_assert(X{0} <= Y{});
>> +static_assert(X{1} <= Y{}); // expected-error {{failed}}
>> +static_assert(X{1} > Y{});
>> +static_assert(X{0} > Y{}); // expected-error {{failed}}
>> +static_assert(X{0} >= Y{});
>> +static_assert(X{-1} >= Y{}); // expected-error {{failed}}
>> +static_assert(Y{} < X{1});
>> +static_assert(Y{} < X{0}); // expected-error {{failed}}
>> +static_assert(Y{} <= X{0});
>> +static_assert(Y{} <= X{-1}); // expected-error {{failed}}
>> +static_assert(Y{} > X{-1});
>> +static_assert(Y{} > X{0}); // expected-error {{failed}}
>> +static_assert(Y{} >= X{0});
>> +static_assert(Y{} >= X{1}); // expected-error {{failed}}
>>
>> Added:
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp?rev=375306&view=auto
>>
>> ==============================================================================
>> ---
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp
>> (added)
>> +++
>> cfe/trunk/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp
>> Fri Oct 18 17:04:43 2019
>> @@ -0,0 +1,38 @@
>> +// RUN: %clang_cc1 -std=c++2a -verify %s
>> +
>> +// ... return type shall be cv bool ...
>> +namespace not_bool {
>> + struct X {} x;
>> + struct Y {} y;
>> + int operator==(X, Y); // expected-note 4{{here}}
>> + bool a = x == y; // ok
>> + bool b = y == x; // expected-error {{return type 'int' of selected
>> 'operator==' function for rewritten '==' comparison is not 'bool'}}
>> + bool c = x != y; // expected-error {{return type 'int' of selected
>> 'operator==' function for rewritten '!=' comparison is not 'bool'}}
>> + bool d = y != x; // expected-error {{return type 'int' of selected
>> 'operator==' function for rewritten '!=' comparison is not 'bool'}}
>> +
>> + // cv-qualifiers are OK
>> + const bool operator==(Y, X);
>> + bool e = y != x; // ok
>> +
>> + // We don't prefer a function with bool return type over one witn
>> non-bool return type.
>> + bool f = x != y; // expected-error {{return type 'int' of selected
>> 'operator==' function for rewritten '!=' comparison is not 'bool'}}
>> +}
>> +
>> +struct X { bool equal; };
>> +struct Y {};
>> +constexpr bool operator==(X x, Y) { return x.equal; }
>> +
>> +static_assert(X{true} == Y{});
>> +static_assert(X{false} == Y{}); // expected-error {{failed}}
>> +
>> +// x == y -> y == x
>> +static_assert(Y{} == X{true});
>> +static_assert(Y{} == X{false}); // expected-error {{failed}}
>> +
>> +// x != y -> !(x == y)
>> +static_assert(X{true} != Y{}); // expected-error {{failed}}
>> +static_assert(X{false} != Y{});
>> +
>> +// x != y -> !(y == x)
>> +static_assert(Y{} != X{true}); // expected-error {{failed}}
>> +static_assert(Y{} != X{false});
>>
>> Modified: cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp (original)
>> +++ cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp Fri Oct 18
>> 17:04:43 2019
>> @@ -1,4 +1,5 @@
>> // RUN: %clang_cc1 -std=c++11 -verify %s
>> +// RUN: %clang_cc1 -std=c++2a -verify %s
>>
>> struct Q { typedef int type; };
>>
>> @@ -20,3 +21,36 @@ template<typename T, template<typename T
>> int &c(...);
>> int &c_disabled = c(0);
>> int &c_enabled = c(Q()); // expected-error {{cannot bind to a temporary
>> of type 'void'}}
>> +
>> +// The substitution proceeds in lexical order and stops when a condition
>> that
>> +// causes deduction to fail is encountered.
>> +#if __cplusplus > 201702L
>> +namespace reversed_operator_substitution_order {
>> + struct X { X(int); };
>> + struct Y { Y(int); };
>> + struct Cat {};
>> + namespace no_adl {
>> + Cat operator<=>(Y, X);
>> + bool operator<(int, Cat);
>> +
>> + template<typename T> struct indirect_sizeof {
>> + static_assert(sizeof(T) != 0);
>> + static const auto value = sizeof(T);
>> + };
>> +
>> + // We should substitute into the construction of the X object before
>> the
>> + // construction of the Y object, so this is a SFINAE case rather
>> than a
>> + // hard error. This requires substitution to proceed in lexical order
>> + // despite the prior rewrite to
>> + // 0 < (Y(...) <=> X(...))
>> + template<typename T> float &f(
>> + decltype(
>> + X(sizeof(T)) < Y(indirect_sizeof<T>::value)
>> + )
>> + );
>> + template<typename T> int &f(...);
>> + }
>> + int &r = no_adl::f<void>(true);
>> + float &s = no_adl::f<int>(true);
>> +}
>> +#endif
>>
>> Added: cfe/trunk/test/CodeGenCXX/mangle-cxx2a.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCXX/mangle-cxx2a.cpp?rev=375306&view=auto
>>
>> ==============================================================================
>> --- cfe/trunk/test/CodeGenCXX/mangle-cxx2a.cpp (added)
>> +++ cfe/trunk/test/CodeGenCXX/mangle-cxx2a.cpp Fri Oct 18 17:04:43 2019
>> @@ -0,0 +1,11 @@
>> +// RUN: %clang_cc1 -emit-llvm %s -o - -triple=x86_64-linux-gnu
>> -std=c++2a | FileCheck %s
>> +
>> +namespace spaceship {
>> + struct X {};
>> + struct Y {};
>> + int operator<=>(X, Y);
>> +
>> + // CHECK-LABEL: define {{.*}}
>> @_ZN9spaceship1fIiEEvDTcmltcvNS_1YE_EcvNS_1XE_EcvT__EE
>> + template<typename T> void f(decltype(Y() < X(), T()) x) {}
>> + template void f<int>(int);
>> +}
>>
>> Modified: cfe/trunk/test/PCH/cxx2a-compare.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/PCH/cxx2a-compare.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/test/PCH/cxx2a-compare.cpp (original)
>> +++ cfe/trunk/test/PCH/cxx2a-compare.cpp Fri Oct 18 17:04:43 2019
>> @@ -15,6 +15,16 @@ inline auto bar(int x) {
>> return (1 <=> x);
>> }
>>
>> +struct X {
>> + int a;
>> + friend constexpr std::strong_ordering operator<=>(const X &x, const X
>> &y) {
>> + return x.a <=> y.a;
>> + }
>> +};
>> +constexpr auto baz(int x) {
>> + return X{3} < X{x};
>> +}
>> +
>> #else
>>
>> // expected-no-diagnostics
>> @@ -25,4 +35,7 @@ auto bar2(int x) {
>> return bar(x);
>> }
>>
>> +static_assert(!baz(3));
>> +static_assert(baz(4));
>> +
>> #endif
>>
>> Modified: cfe/trunk/test/SemaCXX/compare-cxx2a.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/compare-cxx2a.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/test/SemaCXX/compare-cxx2a.cpp (original)
>> +++ cfe/trunk/test/SemaCXX/compare-cxx2a.cpp Fri Oct 18 17:04:43 2019
>> @@ -298,11 +298,11 @@ void test_enum_enum_compare_no_builtin()
>>
>> template <int>
>> struct Tag {};
>> -// expected-note at +1 {{candidate}}
>> -Tag<0> operator<=>(EnumA, EnumA) {
>> +Tag<0> operator<=>(EnumA, EnumA) { // expected-note {{not viable}}
>> return {};
>> }
>> -Tag<1> operator<=>(EnumA, EnumB) {
>> +// expected-note at +1 {{while rewriting comparison as call to
>> 'operator<=>' declared here}}
>> +Tag<1> operator<=>(EnumA, EnumB) { // expected-note {{not viable}}
>> return {};
>> }
>>
>> @@ -311,7 +311,7 @@ void test_enum_ovl_provided() {
>> ASSERT_EXPR_TYPE(r1, Tag<0>);
>> auto r2 = (EnumA::A <=> EnumB::B);
>> ASSERT_EXPR_TYPE(r2, Tag<1>);
>> - (void)(EnumB::B <=> EnumA::A); // expected-error {{invalid operands to
>> binary expression ('EnumCompareTests::EnumB' and
>> 'EnumCompareTests::EnumA')}}
>> + (void)(EnumB::B <=> EnumA::A); // expected-error {{invalid operands to
>> binary expression ('int' and 'Tag<1>')}}
>> }
>>
>> void enum_float_test() {
>>
>> Modified: cfe/trunk/test/SemaCXX/self-comparison.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/self-comparison.cpp?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/test/SemaCXX/self-comparison.cpp (original)
>> +++ cfe/trunk/test/SemaCXX/self-comparison.cpp Fri Oct 18 17:04:43 2019
>> @@ -5,7 +5,7 @@ int foo(int x) {
>> }
>>
>> struct X {
>> - bool operator==(const X &x);
>> + bool operator==(const X &x) const;
>> };
>>
>> struct A {
>>
>> Modified: cfe/trunk/www/cxx_status.html
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/www/cxx_status.html?rev=375306&r1=375305&r2=375306&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/www/cxx_status.html (original)
>> +++ cfe/trunk/www/cxx_status.html Fri Oct 18 17:04:43 2019
>> @@ -920,23 +920,26 @@ as the draft C++2a standard evolves.
>> <tr>
>> <td rowspan="6">Consistent comparison
>> (<tt>operator<=></tt>)</td>
>> <td><a href="http://wg21.link/p0515r3">P0515R3</a></td>
>> - <td rowspan="3" class="partial" align="center">Partial</td>
>> + <td class="partial" align="center">Partial</td>
>> </tr>
>> <tr> <!-- from Jacksonville -->
>> <td><a href="http://wg21.link/p0905r1">P0905R1</a></td>
>> + <td class="svn" align="center">Clang 10</td>
>> </tr>
>> <tr> <!-- from Rapperswil -->
>> <td><a href="http://wg21.link/p1120r0">P1120R0</a></td>
>> + <td rowspan="2" class="partial" align="center">Partial</td>
>> </tr>
>> <tr> <!-- from Kona 2019 -->
>> <td><a href="http://wg21.link/p1185r2">P1185R2</a></td>
>> - <td rowspan="3" class="none" align="center">No</td>
>> </tr>
>> <tr> <!-- from Cologne -->
>> <td><a href="http://wg21.link/p1186r3">P1186R3</a></td>
>> + <td class="none" align="center">No</td>
>> </tr>
>> <tr>
>> <td><a href="http://wg21.link/p1630r1">P1630R1</a></td>
>> + <td class="partial" align="center">Partial</td>
>> </tr>
>> <tr>
>> <td>Access checking on specializations</td>
>>
>>
>> _______________________________________________
>> cfe-commits mailing list
>> cfe-commits at lists.llvm.org
>> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>>
>
>
> --
> --
> Peter
> _______________________________________________
> cfe-commits mailing list
> cfe-commits at lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20200113/36b03f64/attachment-0001.html>
More information about the cfe-commits
mailing list