From 05441b0db7bf23d206dfc865b5c5bde17da1dab0 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Sat, 19 Oct 2019 00:04:43 +0000 Subject: [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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@375306 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/AST/ExprCXX.h | 68 +-- include/clang/Basic/DiagnosticSemaKinds.td | 15 +- include/clang/Basic/OperatorKinds.h | 19 + include/clang/Sema/Overload.h | 94 +++- include/clang/Sema/Sema.h | 55 ++- lib/AST/ExprCXX.cpp | 4 +- lib/Frontend/FrontendActions.cpp | 2 + lib/Sema/SemaExpr.cpp | 7 + lib/Sema/SemaOverload.cpp | 477 ++++++++++++++++----- lib/Sema/SemaTemplate.cpp | 3 +- lib/Sema/SemaTemplateInstantiate.cpp | 7 + lib/Sema/TreeTransform.h | 59 ++- .../over.match.funcs/over.match.oper/p3-2a.cpp | 172 ++++++++ .../over.match.funcs/over.match.oper/p8-2a.cpp | 70 +++ .../over.match.funcs/over.match.oper/p9-2a.cpp | 38 ++ test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp | 34 ++ test/CodeGenCXX/mangle-cxx2a.cpp | 11 + test/PCH/cxx2a-compare.cpp | 13 + test/SemaCXX/compare-cxx2a.cpp | 8 +- test/SemaCXX/self-comparison.cpp | 2 +- www/cxx_status.html | 7 +- 21 files changed, 966 insertions(+), 199 deletions(-) create mode 100644 test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp create mode 100644 test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp create mode 100644 test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp create mode 100644 test/CodeGenCXX/mangle-cxx2a.cpp diff --git a/include/clang/AST/ExprCXX.h b/include/clang/AST/ExprCXX.h index 2b3085183c..7b7ca9bf8f 100644 --- a/include/clang/AST/ExprCXX.h +++ b/include/clang/AST/ExprCXX.h @@ -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 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 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(getPreArg(CONFIG)); + } + CallExpr *getConfig() { return cast_or_null(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 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 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(getPreArg(CONFIG)); - } - CallExpr *getConfig() { return cast_or_null(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 diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 755fac842d..e41cfe2fe7 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -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>, 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 : Error< "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< diff --git a/include/clang/Basic/OperatorKinds.h b/include/clang/Basic/OperatorKinds.h index 9757acaa53..d661891445 100644 --- a/include/clang/Basic/OperatorKinds.h +++ b/include/clang/Basic/OperatorKinds.h @@ -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 diff --git a/include/clang/Sema/Overload.h b/include/clang/Sema/Overload.h index 144e6d927c..a97a7181f7 100644 --- a/include/clang/Sema/Overload.h +++ b/include/clang/Sema/Overload.h @@ -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 Candidates; - llvm::SmallPtrSet Functions; + llvm::SmallPtrSet 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(F->getCanonicalDecl()); + Key |= static_cast(PO); + return Functions.insert(Key).second; } /// Clear out all of the candidates. diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index d3481af50a..1e3d6b93d0 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -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 Args, OverloadCandidateSet &CandidateSet, @@ -3032,7 +3035,8 @@ public: Expr::Classification ObjectClassification, ArrayRef 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 Args, OverloadCandidateSet& CandidateSet, bool SuppressUserConversions = false, - bool PartialOverloading = false); + bool PartialOverloading = false, + OverloadCandidateParamOrder PO = {}); void AddTemplateOverloadCandidate( FunctionTemplateDecl *FunctionTemplate, DeclAccessPair FoundDecl, TemplateArgumentListInfo *ExplicitTemplateArgs, ArrayRef Args, OverloadCandidateSet &CandidateSet, bool SuppressUserConversions = false, bool PartialOverloading = false, bool AllowExplicit = true, - ADLCallKind IsADLCandidate = ADLCallKind::NotADL); - bool CheckNonDependentConversions(FunctionTemplateDecl *FunctionTemplate, - ArrayRef ParamTypes, - ArrayRef 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 ParamTypes, + ArrayRef 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 Args, OverloadCandidateSet& CandidateSet); + void AddNonMemberOperatorCandidates( + const UnresolvedSetImpl &Functions, ArrayRef Args, + OverloadCandidateSet &CandidateSet, + TemplateArgumentListInfo *ExplicitTemplateArgs = nullptr); void AddMemberOperatorCandidates(OverloadedOperatorKind Op, SourceLocation OpLoc, ArrayRef Args, - OverloadCandidateSet& CandidateSet, - SourceRange OpRange = SourceRange()); + OverloadCandidateSet &CandidateSet, + OverloadCandidateParamOrder PO = {}); void AddBuiltinCandidate(QualType *ParamTys, ArrayRef 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 diff --git a/lib/AST/ExprCXX.cpp b/lib/AST/ExprCXX.cpp index 0b0d3c6880..904928bdf2 100644 --- a/lib/AST/ExprCXX.cpp +++ b/lib/AST/ExprCXX.cpp @@ -79,7 +79,7 @@ CXXRewrittenBinaryOperator::getDecomposedForm() const { Result.RHS = BO->getRHS(); Result.InnerBinOp = BO; } else if (auto *BO = dyn_cast(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::getDecomposedForm() const { 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(E)) { assert(BO->getOpcode() == BO_Cmp); Result.LHS = BO->getLHS(); diff --git a/lib/Frontend/FrontendActions.cpp b/lib/Frontend/FrontendActions.cpp index fea5826179..4d47c768ad 100644 --- a/lib/Frontend/FrontendActions.cpp +++ b/lib/Frontend/FrontendActions.cpp @@ -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: diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 333983a430..2734b4d076 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -13310,6 +13310,13 @@ static ExprResult BuildOverloadedBinOp(Sema &S, Scope *Sc, SourceLocation OpLoc, 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); diff --git a/lib/Sema/SemaOverload.cpp b/lib/Sema/SemaOverload.cpp index 80bd3562bc..6793079ff0 100644 --- a/lib/Sema/SemaOverload.cpp +++ b/lib/Sema/SemaOverload.cpp @@ -848,6 +848,25 @@ llvm::Optional DeductionFailureInfo::getCallArgIndex() { } } +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(); +} + 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 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(Function->getType()->getAs()); 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 UnresolvedSetImpl &Fns, 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 UnresolvedSetImpl &Fns, /// 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 Args, - OverloadCandidateSet& CandidateSet, - bool SuppressUserConversions) { + OverloadCandidateSet &CandidateSet, + bool SuppressUserConversions, + OverloadCandidateParamOrder PO) { NamedDecl *Decl = FoundDecl.getDecl(); CXXRecordDecl *ActingContext = cast(Decl->getDeclContext()); @@ -6614,11 +6639,11 @@ void Sema::AddMethodCandidate(DeclAccessPair FoundDecl, AddMethodTemplateCandidate(TD, FoundDecl, ActingContext, /*ExplicitArgs*/ nullptr, ObjectType, ObjectClassification, Args, CandidateSet, - SuppressUserConversions); + SuppressUserConversions, false, PO); } else { AddMethodCandidate(cast(Decl), FoundDecl, ActingContext, ObjectType, ObjectClassification, Args, CandidateSet, - SuppressUserConversions); + SuppressUserConversions, false, None, PO); } } @@ -6637,14 +6662,15 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, OverloadCandidateSet &CandidateSet, bool SuppressUserConversions, bool PartialOverloading, - ConversionSequenceList EarlyConversions) { + ConversionSequenceList EarlyConversions, + OverloadCandidateParamOrder PO) { const FunctionProtoType *Proto = dyn_cast(Method->getType()->getAs()); assert(Proto && "Methods without a prototype cannot be overloaded"); assert(!isa(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 *Method, DeclAccessPair FoundDecl, 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 *Method, DeclAccessPair FoundDecl, // 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 *Method, DeclAccessPair FoundDecl, // 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 *Method, DeclAccessPair FoundDecl, // (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 *Method, DeclAccessPair FoundDecl, // (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 *Method, DeclAccessPair FoundDecl, /// 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 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 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(FunctionTemplateDecl *MethodTmpl, 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(Candidate.Function)->isStatic() || @@ -6829,7 +6858,7 @@ Sema::AddMethodTemplateCandidate(FunctionTemplateDecl *MethodTmpl, AddMethodCandidate(cast(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 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 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 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(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(CXXConversionDecl *Conversion, } } +/// 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 Args, + OverloadCandidateSet &CandidateSet, + TemplateArgumentListInfo *ExplicitTemplateArgs) { + for (UnresolvedSetIterator F = Fns.begin(), E = Fns.end(); F != E; ++F) { + NamedDecl *D = F.getDecl()->getUnderlyingDecl(); + ArrayRef FunctionArgs = Args; + + FunctionTemplateDecl *FunTmpl = dyn_cast(D); + FunctionDecl *FD = + FunTmpl ? FunTmpl->getTemplatedDecl() : cast(D); + + // Don't consider rewritten functions if we're not rewriting. + if (!CandidateSet.getRewriteInfo().isAcceptableCandidate(FD)) + continue; + + assert(!isa(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(CXXConversionDecl *Conversion, void Sema::AddMemberOperatorCandidates(OverloadedOperatorKind Op, SourceLocation OpLoc, ArrayRef 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(OverloadedOperatorKind Op, ++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(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(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 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, NamedDecl *Found, FunctionDecl *Fn, }(); OverloadCandidateKind Kind = [&]() { + if (CRK & CRK_Reversed) + return oc_reversed_binary_operator; + if (CXXConstructorDecl *Ctor = dyn_cast(Fn)) { if (!Ctor->isImplicit()) { if (isa(Found)) @@ -9701,6 +9823,7 @@ bool Sema::checkAddressOfFunctionIsAvailable(const FunctionDecl *Function, // 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(NamedDecl *Found, FunctionDecl *Fn, std::string FnDesc; std::pair 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(Expr *OverloadedExpr, QualType DestType, I != IEnd; ++I) { if (FunctionTemplateDecl *FunTmpl = dyn_cast((*I)->getUnderlyingDecl()) ) { - NoteOverloadCandidate(*I, FunTmpl->getTemplatedDecl(), DestType, + NoteOverloadCandidate(*I, FunTmpl->getTemplatedDecl(), CRK_None, DestType, TakingAddress); } else if (FunctionDecl *Fun = dyn_cast((*I)->getUnderlyingDecl()) ) { - NoteOverloadCandidate(*I, Fun, DestType, TakingAddress); + NoteOverloadCandidate(*I, Fun, CRK_None, DestType, TakingAddress); } } } @@ -9788,7 +9911,8 @@ static void DiagnoseBadConversion(Sema &S, OverloadCandidate *Cand, std::string FnDesc; std::pair 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 &S, NamedDecl *Found, Decl *D, std::string Description; std::pair 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, OverloadCandidate *Cand) { std::string FnDesc; std::pair 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 &S, OverloadCandidate *Cand, if (Fn->isDeleted()) { std::string FnDesc; std::pair 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 &S, OverloadCandidate *Cand, } // 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 &S, OverloadCandidate *Cand, 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 &S, OverloadCandidate *Cand, // 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 CompareOverloadCandidatesForDisplay { /// 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 Args) { +static void +CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand, + ArrayRef Args, + OverloadCandidateSet::CandidateSetKind CSK) { assert(!Cand->Viable); // Don't do anything on failures other than bad conversion. @@ -10834,6 +10962,7 @@ static void CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand, bool SuppressUserConversions = false; unsigned ConvIdx = 0; + unsigned ArgIdx = 0; ArrayRef ParamTypes; if (Cand->IsSurrogate) { @@ -10842,15 +10971,18 @@ static void CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand, if (const PointerType *ConvPtrType = ConvType->getAs()) ConvType = ConvPtrType->getPointeeType(); ParamTypes = ConvType->castAs()->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()->getParamTypes(); if (isa(Cand->Function) && !isa(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(Sema &S, OverloadCandidate *Cand, } // 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 OverloadCandidateSet::CompleteCandidates( 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((*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(SourceLocation OpLoc, UnaryOperatorKind Opc, 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(SourceLocation OpLoc, UnaryOperatorKind Opc, /// /// \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(SourceLocation OpLoc, 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(SourceLocation OpLoc, // 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(FnDecl)) { // Best->Access is only meaningful for class members. @@ -12699,8 +12921,8 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc, 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(SourceLocation OpLoc, isa(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(SourceLocation OpLoc, 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::BuildLiteralOperatorCall(LookupResult &R, 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); diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index 09cc525837..3f2d38630c 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -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::CheckFunctionTemplateSpecialization( // candidates at once, to get proper sorting and limiting. for (auto *OldND : Previous) { if (auto *OldFD = dyn_cast(OldND->getUnderlyingDecl())) - NoteOverloadCandidate(OldND, OldFD, FD->getType(), false); + NoteOverloadCandidate(OldND, OldFD, CRK_None, FD->getType(), false); } FailedCandidates.NoteCandidates(*this, FD->getLocation()); return true; diff --git a/lib/Sema/SemaTemplateInstantiate.cpp b/lib/Sema/SemaTemplateInstantiate.cpp index 42411c9c33..0daa33cfbe 100644 --- a/lib/Sema/SemaTemplateInstantiate.cpp +++ b/lib/Sema/SemaTemplateInstantiate.cpp @@ -206,6 +206,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const { 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 Sema::isSFINAEContext() const { 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; diff --git a/lib/Sema/TreeTransform.h b/lib/Sema/TreeTransform.h index 461cd909c2..89a7b8cc84 100644 --- a/lib/Sema/TreeTransform.h +++ b/lib/Sema/TreeTransform.h @@ -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::TransformBinaryOperator(BinaryOperator *E) { template ExprResult TreeTransform::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(Decomp.LHS)); + if (LHS.isInvalid()) + return ExprError(); + + ExprResult RHS = getDerived().TransformExpr(const_cast(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(Decomp.InnerBinOp)}; + for (Expr *PossibleBinOp : PossibleBinOps) { + auto *Op = dyn_cast(PossibleBinOp->IgnoreImplicit()); + if (!Op) + continue; + auto *Callee = dyn_cast(Op->getCallee()->IgnoreImplicit()); + if (!Callee || isa(Callee->getDecl())) + continue; + + // Transform the callee in case we built a call to a local extern + // declaration. + NamedDecl *Found = cast_or_null(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 diff --git a/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp b/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp new file mode 100644 index 0000000000..3be8dc7749 --- /dev/null +++ b/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp @@ -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 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 struct CRTPBase { + bool operator==(const T&) const; // expected-note {{operator}} + }; + struct CRTP : CRTPBase {}; + 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 diff --git a/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp b/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp new file mode 100644 index 0000000000..da0f576f8c --- /dev/null +++ b/test/CXX/over/over.match/over.match.funcs/over.match.oper/p8-2a.cpp @@ -0,0 +1,70 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +template struct Op { L l; const char *op; R r; }; +// FIXME: Remove once we implement P1816R0. +template Op(L, R) -> Op; + +struct A {}; +struct B {}; +constexpr Op operator<=>(A a, B b) { return {a, "<=>", b}; } + +template constexpr Op, V> operator< (Op a, V b) { return {a, "<", b}; } +template constexpr Op, V> operator<= (Op a, V b) { return {a, "<=", b}; } +template constexpr Op, V> operator> (Op a, V b) { return {a, ">", b}; } +template constexpr Op, V> operator>= (Op a, V b) { return {a, ">=", b}; } +template constexpr Op, V> operator<=>(Op a, V b) { return {a, "<=>", b}; } + +template constexpr Op> operator< (T a, Op b) { return {a, "<", b}; } +template constexpr Op> operator<= (T a, Op b) { return {a, "<=", b}; } +template constexpr Op> operator> (T a, Op b) { return {a, ">", b}; } +template constexpr Op> operator>= (T a, Op b) { return {a, ">=", b}; } +template constexpr Op> operator<=>(T a, Op 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 +constexpr bool same(Op x, Op 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}} diff --git a/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp b/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp new file mode 100644 index 0000000000..fce46816ce --- /dev/null +++ b/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp @@ -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}); diff --git a/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp b/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp index bc074ba25e..f0ab7b8ea7 100644 --- a/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp +++ b/test/CXX/temp/temp.fct.spec/temp.deduct/p7.cpp @@ -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 class ...X> void c(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 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 float &f( + decltype( + X(sizeof(T)) < Y(indirect_sizeof::value) + ) + ); + template int &f(...); + } + int &r = no_adl::f(true); + float &s = no_adl::f(true); +} +#endif diff --git a/test/CodeGenCXX/mangle-cxx2a.cpp b/test/CodeGenCXX/mangle-cxx2a.cpp new file mode 100644 index 0000000000..3e4689948a --- /dev/null +++ b/test/CodeGenCXX/mangle-cxx2a.cpp @@ -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 void f(decltype(Y() < X(), T()) x) {} + template void f(int); +} diff --git a/test/PCH/cxx2a-compare.cpp b/test/PCH/cxx2a-compare.cpp index 9ad368d88f..019544d28c 100644 --- a/test/PCH/cxx2a-compare.cpp +++ b/test/PCH/cxx2a-compare.cpp @@ -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 diff --git a/test/SemaCXX/compare-cxx2a.cpp b/test/SemaCXX/compare-cxx2a.cpp index b6e7fc8061..28854a77fb 100644 --- a/test/SemaCXX/compare-cxx2a.cpp +++ b/test/SemaCXX/compare-cxx2a.cpp @@ -298,11 +298,11 @@ void test_enum_enum_compare_no_builtin() { template struct Tag {}; -// expected-note@+1 {{candidate}} -Tag<0> operator<=>(EnumA, EnumA) { +Tag<0> operator<=>(EnumA, EnumA) { // expected-note {{not viable}} return {}; } -Tag<1> operator<=>(EnumA, EnumB) { +// expected-note@+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() { diff --git a/test/SemaCXX/self-comparison.cpp b/test/SemaCXX/self-comparison.cpp index e20706a8b6..95b830c910 100644 --- a/test/SemaCXX/self-comparison.cpp +++ b/test/SemaCXX/self-comparison.cpp @@ -5,7 +5,7 @@ int foo(int x) { } struct X { - bool operator==(const X &x); + bool operator==(const X &x) const; }; struct A { diff --git a/www/cxx_status.html b/www/cxx_status.html index 322c49a614..70fab7ab34 100755 --- a/www/cxx_status.html +++ b/www/cxx_status.html @@ -920,23 +920,26 @@ as the draft C++2a standard evolves. Consistent comparison (operator<=>) P0515R3 - Partial + Partial P0905R1 + Clang 10 P1120R0 + Partial P1185R2 - No P1186R3 + No P1630R1 + Partial Access checking on specializations -- cgit v1.2.1