Index: include/clang/AST/ASTContext.h =================================================================== --- include/clang/AST/ASTContext.h +++ include/clang/AST/ASTContext.h @@ -18,6 +18,7 @@ #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/CanonicalType.h" #include "clang/AST/CommentCommandTraits.h" +#include "clang/AST/ComparisonCategories.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclarationName.h" @@ -1977,6 +1978,13 @@ QualType GetBuiltinType(unsigned ID, GetBuiltinTypeError &Error, unsigned *IntegerConstantArgs = nullptr) const; + /// \brief Types and expressions required to build C++2a three-way comparisons + /// using operator<=>, including the values return by builtin <=> operators. + /// + /// This object needs to be initialized by Sema the first time it checks + /// a three-way comparison. + ComparisonCategories CompCategories; + private: CanQualType getFromTargetType(unsigned Type) const; TypeInfo getTypeInfoImpl(const Type *T) const; Index: include/clang/AST/ComparisonCategories.h =================================================================== --- /dev/null +++ include/clang/AST/ComparisonCategories.h @@ -0,0 +1,217 @@ +//===- ComparisonCategories.h - Three Way Comparison Data -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the Comparison Category enum and data types, which +// store the types and expressions needed to support operator<=> +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_COMPARISONCATEGORIES_H +#define LLVM_CLANG_AST_COMPARISONCATEGORIES_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/DenseMap.h" +#include +#include + +namespace llvm { + class StringRef; +} + +namespace clang { + +class ASTContext; +class VarDecl; +class RecordDecl; +class QualType; +class NamespaceDecl; + +/// \brief An enumeration representing the different comparison categories +/// types. +/// +/// C++2a [cmp.categories.pre] The types weak_equality, strong_equality, +/// partial_ordering, weak_ordering, and strong_ordering are collectively +/// termed the comparison category types. +enum class ComparisonCategoryType : unsigned char { + WeakEquality, + StrongEquality, + PartialOrdering, + WeakOrdering, + StrongOrdering, + First = WeakEquality, + Last = StrongOrdering +}; + +/// \brief An enumeration representing the possible results of a three-way +/// comparison. These values map onto instances of comparison category types +/// defined in the standard library. i.e. 'std::strong_ordering::less'. +enum class ComparisonCategoryResult : unsigned char { + Equal, + Equivalent, + Nonequivalent, + Nonequal, + Less, + Greater, + Unordered, +}; + +class ComparisonCategoryInfo { + friend class ComparisonCategories; + + const ASTContext &Ctx; + + /// \brief A map containing the comparison category result decls from the + /// standard library. The key is a value of ComparisonCategoryResult. + mutable llvm::DenseMap Objects; + + ComparisonCategoryInfo(const ASTContext &Ctx) : Ctx(Ctx) {} + +public: + /// \brief The declaration for the comparison category type from the + /// standard library. + // FIXME: Make this const + RecordDecl *CCDecl = nullptr; + + /// \brief The Kind of the comparison category type + ComparisonCategoryType Kind; + +public: + /// \brief Return the VarDecl for the specified comparison category result. + /// For example 'std::strong_equality::equal'. + const VarDecl *getResultDecl(ComparisonCategoryResult ValueKind) const { + const VarDecl *VD = lookupResultDecl(ValueKind); + assert(VD && + "comparison category does not contain the specified result kind"); + return VD; + } + + const VarDecl *lookupResultDecl(ComparisonCategoryResult ValueKind) const { + auto &This = *const_cast(this); + return This.lookupResultDecl(ValueKind); + } + + VarDecl *lookupResultDecl(ComparisonCategoryResult ValueKind); + + /// \brief True iff the comparison category is an equality comparison. + bool isEquality() const { return !isOrdered(); } + + /// \brief True iff the comparison category is a relational comparison. + bool isOrdered() const { + using CCK = ComparisonCategoryType; + return Kind == CCK::PartialOrdering || Kind == CCK::WeakOrdering || + Kind == CCK::StrongOrdering; + } + + /// \brief True iff the comparison is "strong". i.e. it checks equality and + /// not equivalence. + bool isStrong() const { + using CCK = ComparisonCategoryType; + return Kind == CCK::StrongEquality || Kind == CCK::StrongOrdering; + } + + /// \brief True iff the comparison is not totally ordered. + bool isPartial() const { + using CCK = ComparisonCategoryType; + return Kind == CCK::PartialOrdering; + } + + /// \brief Converts the specified result kind into the the correct result kind + /// for this category. Specifically it lowers strong equality results to + /// weak equivalence if needed. + ComparisonCategoryResult makeWeakResult(ComparisonCategoryResult Res) const { + using CCR = ComparisonCategoryResult; + if (!isStrong()) { + if (Res == CCR::Equal) + return CCR::Equivalent; + if (Res == CCR::Nonequal) + return CCR::Nonequivalent; + } + return Res; + } + + const VarDecl *getEqualOrEquiv() const { + return getResultDecl(makeWeakResult(ComparisonCategoryResult::Equal)); + } + const VarDecl *getNonequalOrNonequiv() const { + return getResultDecl(makeWeakResult(ComparisonCategoryResult::Nonequal)); + } + const VarDecl *getLess() const { + assert(isOrdered()); + return getResultDecl(ComparisonCategoryResult::Less); + } + const VarDecl *getGreater() const { + assert(isOrdered()); + return getResultDecl(ComparisonCategoryResult::Greater); + } + const VarDecl *getUnordered() const { + assert(isPartial()); + return getResultDecl(ComparisonCategoryResult::Unordered); + } +}; + +class ComparisonCategories { +public: + static StringRef getCategoryString(ComparisonCategoryType Kind); + static StringRef getResultString(ComparisonCategoryResult Kind); + + /// \brief Return the comparison category information for the + /// "common comparison type" for a specified list of types. If there is no + /// such common comparison type, or if any of the specified types are not + /// comparison category types, null is returned. + const ComparisonCategoryInfo * + computeCommonComparisonType(ArrayRef Types) const; + + /// \brief Return the list of results which are valid for the specified + /// comparison category type. + static std::vector + getPossibleResultsForType(ComparisonCategoryType Type); + + /// \brief Return the comparison category information for the category + /// specified by 'Kind'. + const ComparisonCategoryInfo &getInfo(ComparisonCategoryType Kind) const { + const ComparisonCategoryInfo *Result = lookupInfo(Kind); + assert(Result != nullptr && + "information for specified comparison category has not been built"); + return *Result; + } + + /// \brief Return the comparison category information as specified by + /// `getCategoryForType(Ty)`. If the information is not already cached, + /// the declaration is looked up and a cache entry is created. + /// NOTE: Lookup is expected to succeed. Use lookupInfo if failure is possible. + const ComparisonCategoryInfo &getInfoForType(QualType Ty) const; + +public: + /// \brief Return the cached comparison category information for the + /// specified 'Kind'. If no cache entry is present the comparison category + /// type is looked up. If lookup fails nullptr is returned. Otherwise, a + /// new cache entry is created and returned + const ComparisonCategoryInfo *lookupInfo(ComparisonCategoryType Kind) const; + + ComparisonCategoryInfo *lookupInfo(ComparisonCategoryType Kind) { + const auto &This = *this; + return const_cast(This.lookupInfo(Kind)); + } + +private: + const ComparisonCategoryInfo *lookupInfoForType(QualType Ty) const; + +private: + friend class ASTContext; + + explicit ComparisonCategories(const ASTContext &Ctx) : Ctx(Ctx) {} + + const ASTContext &Ctx; + mutable llvm::DenseMap Data; + mutable NamespaceDecl *StdNS = nullptr; +}; + +} // namespace clang + +#endif Index: include/clang/AST/Decl.h =================================================================== --- include/clang/AST/Decl.h +++ include/clang/AST/Decl.h @@ -2162,6 +2162,9 @@ /// true through IsAligned. bool isReplaceableGlobalAllocationFunction(bool *IsAligned = nullptr) const; + /// \brief Determine whether this is a C++2a default comparison operator. + bool isDefaultComparisonOperator(bool IsExplicitlyDefault = false) const; + /// \brief Determine whether this is a destroying operator delete. bool isDestroyingOperatorDelete() const; Index: include/clang/AST/ExprCXX.h =================================================================== --- include/clang/AST/ExprCXX.h +++ include/clang/AST/ExprCXX.h @@ -87,6 +87,8 @@ SourceRange getSourceRangeImpl() const LLVM_READONLY; public: + friend class ASTReader; + friend class ASTWriter; friend class ASTStmtReader; friend class ASTStmtWriter; @@ -4207,6 +4209,138 @@ child_range children() { return child_range(SubExprs, SubExprs + 2); } }; +class CXXRewrittenOperator : public Expr { + friend class ASTReader; + friend class ASTWriter; + friend class ASTStmtReader; + friend class ASTStmtWriter; + + Stmt *SubExprs[2]; + + unsigned Kind : 2; + + CXXRewrittenOperator(EmptyShell Empty) + : Expr(CXXRewrittenOperatorClass, Empty) {} + +public: + typedef BinaryOperatorKind Opcode; + enum RewrittenOperatorKind { ROC_None, ROC_Rewritten, ROC_Synthesized }; + +public: + // FIXME(EricWF): Figure out if this will even be built for dependent + // expressions. + CXXRewrittenOperator(RewrittenOperatorKind Kind, Expr *Underlying, + Expr *Rewritten) + : Expr(CXXRewrittenOperatorClass, Rewritten->getType(), + Rewritten->getValueKind(), Rewritten->getObjectKind(), + /*Dependent*/ false, false, false, false), + Kind(Kind) { + SubExprs[0] = Underlying; + SubExprs[1] = Rewritten; + } + + Expr *getUnderlyingExpr() const { + return static_cast(SubExprs[0]); + } + Expr *getRewrittenExpr() const { + return static_cast(SubExprs[1]); + } + + void setKind(RewrittenOperatorKind xKind) { Kind = xKind; } + RewrittenOperatorKind getKind() const { + return static_cast(Kind); + } + bool isReverseOrder() const LLVM_READONLY { + return getKind() == ROC_Synthesized; + } + + static Expr *getLHSExpr(Expr *E) { + if (auto *UE = dyn_cast(E)) + return UE->getLHS(); + else + return cast(E)->getArg(0); + } + static Expr *getRHSExpr(Expr *E) { + if (auto *UE = dyn_cast(E)) + return UE->getRHS(); + else + return cast(E)->getArg(1); + } + + Expr *getOrigLHS() const { + if (!isReverseOrder()) + return getLHSExpr(getLHSExpr(getRewrittenExpr())); + else + return getRHSExpr(getRHSExpr(getRewrittenExpr())); + } + + Expr *getOrigRHS() const { + if (!isReverseOrder()) + return getRHSExpr(getLHSExpr(getRewrittenExpr())); + else + return getLHSExpr(getRHSExpr(getRewrittenExpr())); + } + + static Opcode getOpcodeFromExpr(Expr *E) { + if (auto *UE = dyn_cast(E)) + return UE->getOpcode(); + else + return BinaryOperator::getOverloadedOpcode( + cast(E)->getOperator()); + } + + Opcode getOrigOpcode() const; + + Expr *getLHS() const { return getLHSExpr(getRewrittenExpr()); } + Expr *getRHS() const { return getRHSExpr(getRewrittenExpr()); } + Opcode getOpcode() const { return getOpcodeFromExpr(getRewrittenExpr()); } + + // Forwarders to the getUnderlyingExpr() expression + + SourceLocation getLocStart() const { + Expr *Underlying = getUnderlyingExpr(); + if (auto *UE = dyn_cast(Underlying)) { + return UE->getLocStart(); + } else { + assert(isa(Underlying)); + return cast(Underlying)->getLocStart(); + } + } + SourceLocation getLocEnd() const { + Expr *Underlying = getUnderlyingExpr(); + if (auto *UE = dyn_cast(Underlying)) { + return UE->getLocEnd(); + } else { + assert(isa(Underlying)); + return cast(Underlying)->getLocEnd(); + } + } + SourceLocation getExprLoc() const { + Expr *Underlying = getUnderlyingExpr(); + if (auto *UE = dyn_cast(Underlying)) { + return UE->getExprLoc(); + } else { + assert(isa(Underlying)); + return cast(Underlying)->getExprLoc(); + } + } + SourceLocation getOperatorLoc() const { + Expr *Underlying = getUnderlyingExpr(); + if (auto *UE = dyn_cast(Underlying)) { + return UE->getOperatorLoc(); + } else { + assert(isa(Underlying)); + return cast(Underlying)->getOperatorLoc(); + } + } + + child_range children() { return child_range(SubExprs, SubExprs + 2); } + + static bool classof(const Stmt *T) { + return T->getStmtClass() == CXXRewrittenOperatorClass; + } +}; + /// \brief Represents an expression that might suspend coroutine execution; /// either a co_await or co_yield expression. /// Index: include/clang/AST/RecursiveASTVisitor.h =================================================================== --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -2549,6 +2549,14 @@ DEF_TRAVERSE_STMT(CXXFoldExpr, {}) DEF_TRAVERSE_STMT(AtomicExpr, {}) +DEF_TRAVERSE_STMT(CXXRewrittenOperator, { + TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getUnderlyingExpr()); + if (!getDerived().shouldVisitImplicitCode()) { + TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getRewrittenExpr()); + } + ShouldVisitChildren = false; +}) + // For coroutines expressions, traverse either the operand // as written or the implied calls, depending on what the // derived class requests. Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -1377,6 +1377,9 @@ def note_pure_qualified_call_kext : Note< "qualified call to %0::%1 is treated as a virtual call to %1 due to -fapple-kext">; +def err_defaulted_decl_not_first : Error< + "defaulted definition must be first declaration">; + def err_deleted_decl_not_first : Error< "deleted definition must be first declaration">; @@ -1660,8 +1663,8 @@ "implicit default constructor suppressed by user-declared constructor">; def note_nontrivial_no_copy : Note< "because no %select{<>|constructor|constructor|assignment operator|" - "assignment operator|<>}2 can be used to " - "%select{<>|copy|move|copy|move|<>}2 " + "assignment operator|<>|<>}2 can be used to " + "%select{<>|copy|move|copy|move|<>|<>}2 " "%select{base class|field|an object}0 of type %3">; def note_nontrivial_user_provided : Note< "because %select{base class of |field of |}0type %1 has a user-provided " @@ -3872,9 +3875,9 @@ "overload resolution selected %select{unavailable|deleted}0 operator '%1'%2">; def err_ovl_deleted_special_oper : Error< "object of type %0 cannot be %select{constructed|copied|moved|assigned|" - "assigned|destroyed}1 because its %select{default constructor|" + "assigned|destroyed|compared}1 because its %select{default constructor|" "copy constructor|move constructor|copy assignment operator|" - "move assignment operator|destructor}1 is implicitly deleted">; + "move assignment operator|destructor|comparison operator}1 is implicitly deleted">; def err_ovl_no_viable_subscript : Error<"no viable overloaded operator[] for type %0">; def err_ovl_no_oper : @@ -4658,11 +4661,11 @@ def err_definition_of_implicitly_declared_member : Error< "definition of implicitly declared %select{default constructor|copy " "constructor|move constructor|copy assignment operator|move assignment " - "operator|destructor|function}1">; + "operator|destructor|comparison operator|function}1">; def err_definition_of_explicitly_defaulted_member : Error< "definition of explicitly defaulted %select{default constructor|copy " "constructor|move constructor|copy assignment operator|move assignment " - "operator|destructor|function}0">; + "operator|destructor|comparison operator|function}0">; def err_redefinition_extern_inline : Error< "redefinition of a 'extern inline' function %0 is not supported in " "%select{C99 mode|C++}1">; @@ -4681,12 +4684,12 @@ def note_deleted_special_member_class_subobject : Note< "%select{default constructor of|copy constructor of|move constructor of|" "copy assignment operator of|move assignment operator of|destructor of|" - "constructor inherited by}0 " + "comparison operator of|constructor inherited by}0 " "%1 is implicitly deleted because " "%select{base class %3|%select{||||variant }4field %3}2 has " "%select{no|a deleted|multiple|an inaccessible|a non-trivial}4 " "%select{%select{default constructor|copy constructor|move constructor|copy " - "assignment operator|move assignment operator|destructor|" + "assignment operator|move assignment operator|destructor|comparison operator|" "%select{default|corresponding|default|default|default}4 constructor}0|" "destructor}5" "%select{||s||}4">; @@ -6439,6 +6442,8 @@ "member %0 declared here">; def note_member_first_declared_here : Note< "member %0 first declared here">; +def note_first_declared_here : Note< + "%0 first declared here">; def err_decrement_bool : Error<"cannot decrement expression of type bool">; def warn_increment_bool : Warning< "incrementing expression of type bool is deprecated and " @@ -7726,13 +7731,16 @@ // C++11 defaulted functions def err_defaulted_special_member_params : Error< - "an explicitly-defaulted %select{|copy |move }0constructor cannot " + "an explicitly-defaulted %select{default constructor|copy constructor|" + "move constructor|<>|<>|<>|comparison operator}0 cannot " "have default arguments">; def err_defaulted_special_member_variadic : Error< - "an explicitly-defaulted %select{|copy |move }0constructor cannot " + "an explicitly-defaulted %select{default constructor|copy constructor|" + "move constructor|<>|<>|<>|comparison operator}0 cannot " "be variadic">; def err_defaulted_special_member_return_type : Error< - "explicitly-defaulted %select{copy|move}0 assignment operator must " + "explicitly-defaulted %select{<>|<>|<>|copy assignment|" + "move assignment|<>|comparison}0 operator must " "return %1">; def err_defaulted_special_member_quals : Error< "an explicitly-defaulted %select{copy|move}0 assignment operator may not " @@ -7740,7 +7748,7 @@ def err_defaulted_special_member_volatile_param : Error< "the parameter for an explicitly-defaulted %select{<>|" "copy constructor|move constructor|copy assignment operator|" - "move assignment operator|<>}0 may not be volatile">; + "move assignment operator|<>|comparison operator}0 may not be volatile">; def err_defaulted_special_member_move_const_param : Error< "the parameter for an explicitly-defaulted move " "%select{constructor|assignment operator}0 may not be const">; @@ -7754,11 +7762,12 @@ def err_incorrect_defaulted_exception_spec : Error< "exception specification of explicitly defaulted %select{default constructor|" "copy constructor|move constructor|copy assignment operator|move assignment " - "operator|destructor}0 does not match the " + "operator|destructor|comparison operator}0 does not match the " "calculated one">; def err_incorrect_defaulted_constexpr : Error< "defaulted definition of %select{default constructor|copy constructor|" - "move constructor|copy assignment operator|move assignment operator}0 " + "move constructor|copy assignment operator|move assignment operator|" + "destructor|comparison operator}0 " "is not constexpr">; def err_out_of_line_default_deletes : Error< "defaulting this %select{default constructor|copy constructor|move " @@ -7776,6 +7785,17 @@ "specification redeclared with an %select{implicit|explicit}0 exception " "specification">, InGroup>; +// C++2a defaulted comparison operators +def err_defaulted_comparison_operator_template : Error< + "an explicitly-defaulted comparison operator cannot be a template">; +def err_defaulted_comparison_operator_decl_scope : Error< + "an explicitly-defaulted comparison operator not declared at class scope">; +def err_defaulted_comparison_operator_param_type : Error< + "the parameter for this explicitly-defaulted comparison operator must have " + "type %0, but parameter has type %1">; +def err_defaulted_comparison_operator_member_non_const : Error< + "explicitly-defaulted comparison operator is not const qualified">; + def warn_ptr_arith_precedes_bounds : Warning< "the pointer decremented by %0 refers before the beginning of the array">, InGroup, DefaultIgnore; @@ -9396,4 +9416,21 @@ def err_multiversion_not_supported : Error< "function multiversioning is not supported on the current target">; +// three-way comparison operator diagnostics +def err_implied_comparison_category_type_not_found : Error< + "cannot deduce return type of 'operator<=>' because type %0 was not found; " + "include ">; +def err_spaceship_argument_narrowing : Error< + "argument to 'operator<=>' " + "%select{cannot be narrowed from type %1 to %2|" + "evaluates to %1, which cannot be narrowed to type %2}0">; +def err_std_compare_type_missing_member : Error< + "%0 is missing a member named '%1'">; +def err_std_compare_type_not_supported : Error< + "standard library implementation of %0 is not supported; " + "member '%1' does not have expected form">; +def ext_default_comparison_op : ExtWarn< + "defaulted comparison operator functions are a C++2a extension">, + InGroup; + } // end of sema component. Index: include/clang/Basic/StmtNodes.td =================================================================== --- include/clang/Basic/StmtNodes.td +++ include/clang/Basic/StmtNodes.td @@ -146,6 +146,7 @@ def MaterializeTemporaryExpr : DStmt; def LambdaExpr : DStmt; def CXXFoldExpr : DStmt; +def CXXRewrittenOperator : DStmt; // C++ Coroutines TS expressions def CoroutineSuspendExpr : DStmt; Index: include/clang/Sema/Overload.h =================================================================== --- include/clang/Sema/Overload.h +++ include/clang/Sema/Overload.h @@ -72,6 +72,17 @@ OCD_ViableCandidates }; + /// OperatorOverloadCandidateKind - The kind of the operator candidate in + /// accordance with [over.match.oper]. + enum RewrittenOverloadCandidateKind : unsigned char { + /// Not a rewritten candidate. + ROC_None, + /// Rewritten but not synthesized. + ROC_Rewritten, + /// Both rewritten and synthesized. + ROC_Synthesized + }; + /// 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 @@ -330,9 +341,10 @@ } ImplicitConversionRank getRank() const; - NarrowingKind getNarrowingKind(ASTContext &Context, const Expr *Converted, - APValue &ConstantValue, - QualType &ConstantType) const; + NarrowingKind + getNarrowingKind(ASTContext &Context, const Expr *Converted, + APValue &ConstantValue, QualType &ConstantType, + bool IgnoreFloatToIntegralConversion = false) const; bool isPointerConversionToBool() const; bool isPointerConversionToVoidPointer(ASTContext& Context) const; void dump() const; @@ -754,12 +766,12 @@ ConversionFixItGenerator Fix; /// Viable - True to indicate that this overload candidate is viable. - bool Viable; + bool Viable : 1; /// IsSurrogate - True to indicate that this candidate is a /// surrogate for a conversion to a function pointer or reference /// (C++ [over.call.object]). - bool IsSurrogate; + bool IsSurrogate : 1; /// IgnoreObjectArgument - True to indicate that the first /// argument's conversion, which for this function represents the @@ -768,7 +780,11 @@ /// implicit object argument is just a placeholder) or a /// non-static member function when the call doesn't have an /// object argument. - bool IgnoreObjectArgument; + bool IgnoreObjectArgument : 1; + + /// RewrittenKind - For rewritten operator candidates, the kind of rewritten + /// candidate it is: rewritten or synthesized. + unsigned char RewrittenOpKind : 2; /// FailureKind - The reason why this candidate is not viable. /// Actually an OverloadFailureKind. @@ -811,6 +827,24 @@ return CanFix; } + /// \brief Return the index of the conversion corresponding to the specified + /// argument index. If this is not a synthesized candidate, 'Idx' is + /// returned. Otherwise the index corresponding to the reversed parameter + /// is returned. + unsigned getConversionIndexForArgIndex(unsigned Idx) const; + + /// \brief Return the conversion sequence for the specified argument index. + /// If this is a synthesized candidate, the argument index is reversed. + const ImplicitConversionSequence &getConversion(unsigned ArgIdx) const; + + /// \brief Returns the parameter type corresponding to the specified index. + /// (The index is not reversed for synthesized candidates). + QualType getParamType(unsigned Idx) const { + if (Function) + return Function->getParamDecl(Idx)->getType(); + return BuiltinParamTypes[Idx]; + } + unsigned getNumParams() const { if (IsSurrogate) { auto STy = Surrogate->getConversionType(); @@ -822,6 +856,10 @@ return Function->getNumParams(); return ExplicitCallArguments; } + + RewrittenOverloadCandidateKind getRewrittenKind() const { + return static_cast(RewrittenOpKind); + } }; /// OverloadCandidateSet - A set of overload candidates, used in C++ @@ -852,8 +890,10 @@ private: SmallVector Candidates; - llvm::SmallPtrSet Functions; + using DeclSet = llvm::SmallPtrSet; + DeclSet Functions; + private: // Allocator for ConversionSequenceLists. We store the first few of these // inline to avoid allocation for small sets. llvm::BumpPtrAllocator SlabAllocator; @@ -895,6 +935,31 @@ void destroyCandidates(); public: + /// \brief RewrittenCandidateContextGuard - Enter a context suitable for + /// adding rewritten overload candidates. Rewritten candidates can + /// re-consider previously seen functions, so save and clear the list of + /// considered functions, and restore it when the rewritten context is + /// exited. + struct RewrittenCandidateContextGuard { + RewrittenCandidateContextGuard(OverloadCandidateSet &CS) + : CandidateSet(CS) { + assert(CS.Kind == CSK_Operator && + "rewritten expressions can only occur for operators"); + OldFunctions = std::move(CandidateSet.Functions); + } + + ~RewrittenCandidateContextGuard() { + CandidateSet.Functions.insert(OldFunctions.begin(), OldFunctions.end()); + } + + private: + OverloadCandidateSet &CandidateSet; + DeclSet OldFunctions; + }; + + friend struct RewrittenCandidateContextGuard; + + public: OverloadCandidateSet(SourceLocation Loc, CandidateSetKind CSK) : Loc(Loc), Kind(CSK) {} OverloadCandidateSet(const OverloadCandidateSet &) = delete; @@ -951,8 +1016,9 @@ } /// Find the best viable function on this overload set, if it exists. - OverloadingResult BestViableFunction(Sema &S, SourceLocation Loc, - OverloadCandidateSet::iterator& Best); + OverloadingResult BestViableFunction( + Sema &S, SourceLocation Loc, OverloadCandidateSet::iterator &Best, + SmallVectorImpl *EquivalentCands = nullptr); void NoteCandidates(Sema &S, OverloadCandidateDisplayKind OCD, Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -17,8 +17,9 @@ #include "clang/AST/Attr.h" #include "clang/AST/Availability.h" -#include "clang/AST/DeclarationName.h" +#include "clang/AST/ComparisonCategories.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/DeclarationName.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ExternalASTSource.h" @@ -607,14 +608,15 @@ SmallVector, 2> DelayedExceptionSpecChecks; - /// \brief All the members seen during a class definition which were both - /// explicitly defaulted and had explicitly-specified exception - /// specifications, along with the function type containing their - /// user-specified exception specification. Those exception specifications - /// were overridden with the default specifications, but we still need to - /// check whether they are compatible with the default specification, and - /// we can't do that until the nesting set of class definitions is complete. - SmallVector, 2> + /// \brief All the members or comparison friend functions seen during a + /// class definition which were both explicitly defaulted and had + /// explicitly-specified exception specifications, along with the function + /// type containing their user-specified exception specification. Those + /// exception specifications were overridden with the default specifications, + /// but we still need to check whether they are compatible with the default + /// specification, and we can't do that until the nesting set of class + /// definitions is complete. + SmallVector, 2> DelayedDefaultedMemberExceptionSpecs; typedef llvm::MapVector Pair; + union { + FunctionDecl *Function; + void *BuiltinArgType; + }; + unsigned char LookupKind : 2; + unsigned char IsBuiltin : 1; public: - SpecialMemberOverloadResult() : Pair() {} - SpecialMemberOverloadResult(CXXMethodDecl *MD) - : Pair(MD, MD->isDeleted() ? NoMemberOrDeleted : Success) {} - - CXXMethodDecl *getMethod() const { return Pair.getPointer(); } - void setMethod(CXXMethodDecl *MD) { Pair.setPointer(MD); } - - Kind getKind() const { return static_cast(Pair.getInt()); } - void setKind(Kind K) { Pair.setInt(K); } + SpecialMemberOverloadResult() + : Function(nullptr), LookupKind(0), IsBuiltin(false) {} + SpecialMemberOverloadResult(FunctionDecl *FD) + : Function(FD), + LookupKind(FD->isDeleted() ? NoMemberOrDeleted : Success), + IsBuiltin(false) {} + SpecialMemberOverloadResult(QualType Ty) + : BuiltinArgType(Ty.getAsOpaquePtr()), LookupKind(Success), + IsBuiltin(true) {} + + FunctionDecl *getFunction() const { + if (IsBuiltin) + return nullptr; + return Function; + } + void setFunction(FunctionDecl *FD) { + Function = FD; + IsBuiltin = false; + } + QualType getBuiltinArgType() const { + if (!IsBuiltin) + return QualType(); + return QualType::getFromOpaquePtr(BuiltinArgType); + } + void setBuiltinArgType(QualType Ty) { + BuiltinArgType = Ty.getAsOpaquePtr(); + IsBuiltin = true; + } + bool hasBuiltin() const { return IsBuiltin; } + bool hasFunction() const { return !IsBuiltin && Function; } + + Kind getKind() const { return static_cast(LookupKind); } + void setKind(Kind K) { LookupKind = K; } }; class SpecialMemberOverloadResultEntry @@ -1123,7 +1154,8 @@ /// of -Wselector. llvm::MapVector ReferencedSelectors; - /// Kinds of C++ special members. + /// Kind of C++ members which can be defaulted. Including special members and + /// explicitly defaulted comparison operators. enum CXXSpecialMember { CXXDefaultConstructor, CXXCopyConstructor, @@ -1131,6 +1163,7 @@ CXXCopyAssignment, CXXMoveAssignment, CXXDestructor, + CXXComparisonOperator, CXXInvalid }; @@ -2231,10 +2264,11 @@ TAH_ConsiderTrivialABI }; - bool SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMember CSM, + bool SpecialMemberIsTrivial(FunctionDecl *FD, CXXSpecialMember CSM, TrivialABIHandling TAH = TAH_IgnoreTrivialABI, bool Diagnose = false); - CXXSpecialMember getSpecialMember(const CXXMethodDecl *MD); + CXXSpecialMember getSpecialMember(const FunctionDecl *FD, + bool IsExplicitlyDefault = false); void ActOnLastBitfield(SourceLocation DeclStart, SmallVectorImpl &AllIvarDecls); Decl *ActOnIvar(Scope *S, SourceLocation DeclStart, @@ -2776,6 +2810,12 @@ void AddBuiltinOperatorCandidates(OverloadedOperatorKind Op, SourceLocation OpLoc, ArrayRef Args, OverloadCandidateSet& CandidateSet); + void AddRewrittenOperatorCandidates(OverloadedOperatorKind Op, + SourceLocation OpLoc, + ArrayRef Args, + const UnresolvedSetImpl &Fns, + OverloadCandidateSet &CandidateSet, + bool PerformADL); void AddArgumentDependentLookupCandidates(DeclarationName Name, SourceLocation Loc, ArrayRef Args, @@ -2918,11 +2958,10 @@ const UnresolvedSetImpl &Fns, Expr *input, bool RequiresADL = true); - ExprResult CreateOverloadedBinOp(SourceLocation OpLoc, - BinaryOperatorKind Opc, - const UnresolvedSetImpl &Fns, - Expr *LHS, Expr *RHS, - bool RequiresADL = true); + ExprResult CreateOverloadedBinOp(SourceLocation OpLoc, BinaryOperatorKind Opc, + const UnresolvedSetImpl &Fns, Expr *LHS, + Expr *RHS, bool RequiresADL = true, + bool AllowRewrittenCandidates = true); ExprResult CreateOverloadedArraySubscriptExpr(SourceLocation LLoc, SourceLocation RLoc, @@ -3181,6 +3220,14 @@ bool RValueThis, unsigned ThisQuals); CXXDestructorDecl *LookupDestructor(CXXRecordDecl *Class); + /// \brief Lookup the three-way comparison operator for the specified ArgTy. + /// The comparison is homogenious as required for explicitly-defaulted + /// comparison operators. ArgTy is transformed into a const lvalue before + /// lookup is performed. + SpecialMemberOverloadResult LookupThreeWayComparison(CXXRecordDecl *RD, + QualType ArgTy, + SourceLocation Loc); + bool checkLiteralOperatorId(const CXXScopeSpec &SS, const UnqualifiedId &Id); LiteralOperatorLookupResult LookupLiteralOperator(Scope *S, LookupResult &R, ArrayRef ArgTys, @@ -3188,6 +3235,7 @@ bool AllowTemplate, bool AllowStringTemplate, bool DiagnoseMissing); + bool isKnownName(StringRef name); void ArgumentDependentLookup(DeclarationName Name, SourceLocation Loc, @@ -4545,6 +4593,32 @@ CXXRecordDecl *getStdBadAlloc() const; EnumDecl *getStdAlignValT() const; +private: + // This stores ComparisonCategoryInfo pointers from ASTContext's cache that + // we have already fully checked, and don't need to check again. + llvm::SmallPtrSet(ComparisonCategoryType::Last) + 1> + FullyCheckedComparisonCategories; + +public: + /// \brief Lookup the specified comparison category types in the standard + /// library, an check the VarDecls possibly returned by the operator<=> + /// builtins for that type. + /// + /// \return The type of the comparison category type corresponding to the + /// specified Kind, or a null type if an error occurs + QualType CheckComparisonCategoryType(ComparisonCategoryType Kind, + SourceLocation Loc); + + /// \brief Compute the 'common comparison type' (C++2a [class.spaceship]) for + /// the specified list of types. If any of the required comparison category + /// types or declarations are not found, a diagnostic is emitted. + /// + /// \return The common comparison type, if no error occurs. Otherwise a null + /// type. + QualType ComputeCommonComparisonType(ArrayRef Types, + SourceLocation Loc); + /// \brief Tests whether Ty is an instance of std::initializer_list and, if /// it is and Element is not NULL, assigns the element type to Element. bool isStdInitializerList(QualType Ty, QualType *Element); @@ -4728,7 +4802,7 @@ const QualType *data() const { return Exceptions.data(); } /// \brief Integrate another called method into the collected data. - void CalledDecl(SourceLocation CallLoc, const CXXMethodDecl *Method); + void CalledDecl(SourceLocation CallLoc, const FunctionDecl *FD); /// \brief Integrate an invoked expression into the collected data. void CalledExpr(Expr *E); @@ -4793,7 +4867,7 @@ /// \brief Evaluate the implicit exception specification for a defaulted /// special member function. - void EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD); + void EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD); /// \brief Check the given exception-specification and update the /// exception specification information with the results. @@ -4821,9 +4895,9 @@ class InheritedConstructorInfo; - /// \brief Determine if a special member function should have a deleted - /// definition when it is defaulted. - bool ShouldDeleteSpecialMember(CXXMethodDecl *MD, CXXSpecialMember CSM, + /// \brief Determine if a special member function or defaulted comparison + /// operator, should have a deleted definition when it is defaulted. + bool ShouldDeleteSpecialMember(FunctionDecl *FD, CXXSpecialMember CSM, InheritedConstructorInfo *ICI = nullptr, bool Diagnose = false); @@ -4949,6 +5023,17 @@ /// it simply returns the passed in expression. ExprResult MaybeBindToTemporary(Expr *E); + /// \brief Check whether the specified explicitly-defaulted comparison + /// declaration is ollowed. + /// + /// \return true if an error occurred + bool checkDefaultedComparisonOperatorDecl(SourceLocation DefaultLoc, + Decl *Dcl); + + /// \brief Defines a explicitly-defaulted comparison operator. + void DefineDefaultedComparisonOperator(SourceLocation CurrentLocation, + FunctionDecl *FD); + bool CompleteConstructorCall(CXXConstructorDecl *Constructor, MultiExprArg ArgsPtr, SourceLocation Loc, @@ -5878,8 +5963,8 @@ StorageClass &SC); void CheckDeductionGuideTemplate(FunctionTemplateDecl *TD); - void CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD); - void CheckExplicitlyDefaultedMemberExceptionSpec(CXXMethodDecl *MD, + void CheckExplicitlyDefaultedMember(FunctionDecl *FD); + void CheckExplicitlyDefaultedMemberExceptionSpec(FunctionDecl *FD, const FunctionProtoType *T); void CheckDelayedMemberExceptionSpecs(); @@ -9568,7 +9653,7 @@ BinaryOperatorKind Opc, bool IsCompAssign = false); QualType CheckCompareOperands( // C99 6.5.8/9 ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, - BinaryOperatorKind Opc, bool isRelational); + BinaryOperatorKind Opc); QualType CheckBitwiseOperands( // C99 6.5.[10...12] ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, BinaryOperatorKind Opc); @@ -10337,11 +10422,13 @@ const FunctionProtoType *Proto, SourceLocation Loc); +public: void checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, const Expr *ThisArg, ArrayRef Args, bool IsMemberFunction, SourceLocation Loc, SourceRange Range, VariadicCallType CallType); +private: bool CheckObjCString(Expr *Arg); ExprResult CheckOSLogFormatStringArg(Expr *Arg); Index: include/clang/Serialization/ASTBitCodes.h =================================================================== --- include/clang/Serialization/ASTBitCodes.h +++ include/clang/Serialization/ASTBitCodes.h @@ -1808,6 +1808,7 @@ EXPR_FUNCTION_PARM_PACK, // FunctionParmPackExpr EXPR_MATERIALIZE_TEMPORARY, // MaterializeTemporaryExpr EXPR_CXX_FOLD, // CXXFoldExpr + EXPR_CXX_REWRITTEN_OPERATOR, // CXXRewrittenOperator // CUDA EXPR_CUDA_KERNEL_CALL, // CUDAKernelCallExpr Index: lib/AST/ASTContext.cpp =================================================================== --- lib/AST/ASTContext.cpp +++ lib/AST/ASTContext.cpp @@ -792,7 +792,8 @@ LangOpts.XRayAttrListFiles, SM)), PrintingPolicy(LOpts), Idents(idents), Selectors(sels), BuiltinInfo(builtins), DeclarationNames(*this), Comments(SM), - CommentCommandTraits(BumpAlloc, LOpts.CommentOpts), LastSDM(nullptr, 0) { + CommentCommandTraits(BumpAlloc, LOpts.CommentOpts), + CompCategories(this_()), LastSDM(nullptr, 0) { TUDecl = TranslationUnitDecl::Create(*this); } Index: lib/AST/CMakeLists.txt =================================================================== --- lib/AST/CMakeLists.txt +++ lib/AST/CMakeLists.txt @@ -20,6 +20,7 @@ CommentLexer.cpp CommentParser.cpp CommentSema.cpp + ComparisonCategories.cpp DataCollection.cpp Decl.cpp DeclarationName.cpp Index: lib/AST/ComparisonCategories.cpp =================================================================== --- /dev/null +++ lib/AST/ComparisonCategories.cpp @@ -0,0 +1,235 @@ +//===- ComparisonCategories.cpp - Three Way Comparison Data -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the Comparison Category enum and data types, which +// store the types and expressions needed to support operator<=> +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ComparisonCategories.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Type.h" +#include "llvm/ADT/SmallVector.h" + +using namespace clang; + +VarDecl * +ComparisonCategoryInfo::lookupResultDecl(ComparisonCategoryResult ValueKind) { + char Key = static_cast(ValueKind); + VarDecl *VD = Objects.lookup(Key); + if (VD) + return VD; + + const RecordDecl *RD = cast(CCDecl->getCanonicalDecl()); + StringRef Name = ComparisonCategories::getResultString(ValueKind); + DeclContextLookupResult Lookup = RD->lookup(&Ctx.Idents.get(Name)); + if (Lookup.size() != 1) + return nullptr; + NamedDecl *ND = Lookup.front(); + if (auto *VD = dyn_cast(ND)) { + auto ItPair = + Objects.try_emplace((char)ValueKind, VD); + return ItPair.first->second; + } + return nullptr; +} + +static const NamespaceDecl *lookupStdNamespace(const ASTContext &Ctx, + NamespaceDecl *&StdNS) { + if (!StdNS) { + DeclContextLookupResult Lookup = + Ctx.getTranslationUnitDecl()->lookup(&Ctx.Idents.get("std")); + if (Lookup.size() == 1) + StdNS = dyn_cast(Lookup.front()); + } + return StdNS; +} + +static RecordDecl *lookupRecordDecl(const ASTContext &Ctx, + const NamespaceDecl *StdNS, + ComparisonCategoryType Kind) { + StringRef Name = ComparisonCategories::getCategoryString(Kind); + DeclContextLookupResult Lookup = StdNS->lookup(&Ctx.Idents.get(Name)); + if (Lookup.size() == 1) { + if (RecordDecl *RD = dyn_cast(Lookup.front())) + return RD; + } + return nullptr; +} + +const ComparisonCategoryInfo * +ComparisonCategories::lookupInfo(ComparisonCategoryType Kind) const { + auto It = Data.find(static_cast(Kind)); + if (It != Data.end()) + return &It->second; + if (const NamespaceDecl *NS = lookupStdNamespace(Ctx, StdNS)) { + if (RecordDecl *RD = lookupRecordDecl(Ctx, NS, Kind)) { + ComparisonCategoryInfo Info(Ctx); + Info.CCDecl = RD; + Info.Kind = Kind; + return &Data.try_emplace((char)Kind, std::move(Info)).first->second; + } + } + return nullptr; +} + +const ComparisonCategoryInfo * +ComparisonCategories::lookupInfoForType(QualType Ty) const { + assert(!Ty.isNull() && "type must be non-null"); + using CCT = ComparisonCategoryType; + auto *RD = Ty->getAsCXXRecordDecl(); + if (!RD) + return nullptr; + + // Check to see if we have information for the specified type cached. + const auto *CanonRD = RD->getCanonicalDecl(); + for (auto &KV : Data) { + const ComparisonCategoryInfo &Info = KV.second; + if (CanonRD == Info.CCDecl->getCanonicalDecl()) + return &Info; + } + + if (!RD->getEnclosingNamespaceContext()->isStdNamespace()) + return nullptr; + + // If not, check to see if the decl names a type in namespace std with a name + // matching one of the comparison category types. + for (unsigned I = static_cast(CCT::First), + End = static_cast(CCT::Last); + I <= End; ++I) { + CCT Kind = static_cast(I); + + // We've found the comparison category type. Build a new cache entry for + // it. + if (getCategoryString(Kind) == RD->getName()) { + ComparisonCategoryInfo Info(Ctx); + Info.CCDecl = RD; + Info.Kind = Kind; + return &Data.try_emplace((char)Kind, std::move(Info)).first->second; + } + } + + // We've found nothing. This isn't a comparison category type. + return nullptr; +} + +const ComparisonCategoryInfo &ComparisonCategories::getInfoForType(QualType Ty) const { + const ComparisonCategoryInfo *Info = lookupInfoForType(Ty); + assert(Info && "info for comparison category not found"); + return *Info; +} + +StringRef ComparisonCategories::getCategoryString(ComparisonCategoryType Kind) { + using CCKT = ComparisonCategoryType; + switch (Kind) { + case CCKT::WeakEquality: + return "weak_equality"; + case CCKT::StrongEquality: + return "strong_equality"; + case CCKT::PartialOrdering: + return "partial_ordering"; + case CCKT::WeakOrdering: + return "weak_ordering"; + case CCKT::StrongOrdering: + return "strong_ordering"; + } + llvm_unreachable("unhandled cases in switch"); +} + +StringRef ComparisonCategories::getResultString(ComparisonCategoryResult Kind) { + using CCVT = ComparisonCategoryResult; + switch (Kind) { + case CCVT::Equal: + return "equal"; + case CCVT::Nonequal: + return "nonequal"; + case CCVT::Equivalent: + return "equivalent"; + case CCVT::Nonequivalent: + return "nonequivalent"; + case CCVT::Less: + return "less"; + case CCVT::Greater: + return "greater"; + case CCVT::Unordered: + return "unordered"; + } + llvm_unreachable("unhandled case in switch"); +} + +std::vector +ComparisonCategories::getPossibleResultsForType(ComparisonCategoryType Type) { + using CCT = ComparisonCategoryType; + using CCR = ComparisonCategoryResult; + std::vector Values; + Values.reserve(6); + Values.push_back(CCR::Equivalent); + bool IsStrong = (Type == CCT::StrongEquality || Type == CCT::StrongOrdering); + if (IsStrong) + Values.push_back(CCR::Equal); + if (Type == CCT::StrongOrdering || Type == CCT::WeakOrdering || + Type == CCT::PartialOrdering) { + Values.push_back(CCR::Less); + Values.push_back(CCR::Greater); + } else { + Values.push_back(CCR::Nonequivalent); + if (IsStrong) + Values.push_back(CCR::Nonequal); + } + if (Type == CCT::PartialOrdering) + Values.push_back(CCR::Unordered); + return Values; +} + +/// C++2a [class.spaceship]p4 - compute the common category type. +const ComparisonCategoryInfo *ComparisonCategories::computeCommonComparisonType( + ArrayRef Types) const { + using CCT = ComparisonCategoryType; + std::array(CCT::Last) + 1> Seen = {}; + auto Count = [&](CCT T) { return Seen[static_cast(T)]; }; + + // Count the number of times each comparison category type occurs in the + // specified type list. If any type is not a comparison category, return + // nullptr. + for (auto Ty : Types) { + const ComparisonCategoryInfo *Info = lookupInfoForType(Ty); + // --- If any T is not a comparison category type, U is void. + if (!Info) + return nullptr; + Seen[static_cast(Info->Kind)]++; + } + // --- Otherwise, if at least one Ti is std::weak_equality, or at least one + // Ti is std::strong_equality and at least one Tj is + // std::partial_ordering or std::weak_ordering, U is + // std::weak_equality. + if (Count(CCT::WeakEquality) || + (Count(CCT::StrongEquality) && + (Count(CCT::PartialOrdering) || Count(CCT::WeakOrdering)))) + return lookupInfo(CCT::WeakEquality); + + // --- Otherwise, if at least one Ti is std::strong_equality, U is + // std::strong_equality + if (Count(CCT::StrongEquality)) + return lookupInfo(CCT::StrongEquality); + + // --- Otherwise, if at least one Ti is std::partial_ordering, U is + // std::partial_ordering. + if (Count(CCT::PartialOrdering)) + return lookupInfo(CCT::PartialOrdering); + + // --- Otherwise, if at least one Ti is std::weak_ordering, U is + // std::weak_ordering. + if (Count(CCT::WeakOrdering)) + return lookupInfo(CCT::WeakOrdering); + + // FIXME(EricWF): What if we don't find std::strong_ordering + // --- Otherwise, U is std::strong_ordering. + return lookupInfo(CCT::StrongOrdering); +} Index: lib/AST/Decl.cpp =================================================================== --- lib/AST/Decl.cpp +++ lib/AST/Decl.cpp @@ -2809,6 +2809,21 @@ RD->getIdentifier()->isStr("destroying_delete_t"); } +bool FunctionDecl::isDefaultComparisonOperator(bool IsExplicitlyDefault) const { + // C++2a [class.campare.default] A defaulted comparison operator function + // ([expr.spaceship], [expr.rel], [expr.eq]) for some class C shall be a + // non-template function declared in the member-specification of C that is + // (1.1) a non-static member of C having one parameter of type const C&, or + if ((!isDefaulted() && !IsExplicitlyDefault) || !isOverloadedOperator()) + return false; + if (!isa(this) && !isa(getLexicalParent())) + return false; + auto Opc = BinaryOperator::getOverloadedOpcode(getOverloadedOperator()); + if (BinaryOperator::isComparisonOp(Opc)) + return true; + return false; +} + LanguageLinkage FunctionDecl::getLanguageLinkage() const { return getDeclLanguageLinkage(*this); } Index: lib/AST/Expr.cpp =================================================================== --- lib/AST/Expr.cpp +++ lib/AST/Expr.cpp @@ -3187,6 +3187,11 @@ return false; } + case CXXRewrittenOperatorClass: { + const auto *RO = cast(this); + return RO->getRewrittenExpr()->HasSideEffects(Ctx, IncludePossibleEffects); + } + case PseudoObjectExprClass: { // Only look for side-effects in the semantic form, and look past // OpaqueValueExpr bindings in that form. Index: lib/AST/ExprClassification.cpp =================================================================== --- lib/AST/ExprClassification.cpp +++ lib/AST/ExprClassification.cpp @@ -287,7 +287,9 @@ if (cast(E)->isResultDependent()) return Cl::CL_PRValue; return ClassifyInternal(Ctx,cast(E)->getResultExpr()); - + case Expr::CXXRewrittenOperatorClass: + return ClassifyInternal(Ctx, + cast(E)->getRewrittenExpr()); case Expr::BinaryOperatorClass: case Expr::CompoundAssignOperatorClass: // C doesn't have any binary expressions that are lvalues. Index: lib/AST/ExprConstant.cpp =================================================================== --- lib/AST/ExprConstant.cpp +++ lib/AST/ExprConstant.cpp @@ -2242,6 +2242,8 @@ case BO_GE: Result = LHS >= RHS; return true; case BO_EQ: Result = LHS == RHS; return true; case BO_NE: Result = LHS != RHS; return true; + case BO_Cmp: + llvm_unreachable("BO_Cmp should be handled elsewhere"); } } @@ -5057,9 +5059,13 @@ return; VisitIgnoredValue(E); } -}; + bool VisitCXXRewrittenOperator(const CXXRewrittenOperator *E) { + return StmtVisitorTy::Visit(E->getRewrittenExpr()); } +}; + +} // namespace //===----------------------------------------------------------------------===// // Common base class for lvalue and temporary evaluation. @@ -6232,6 +6238,8 @@ bool VisitCXXInheritedCtorInitExpr(const CXXInheritedCtorInitExpr *E); bool VisitCXXConstructExpr(const CXXConstructExpr *E, QualType T); bool VisitCXXStdInitializerListExpr(const CXXStdInitializerListExpr *E); + + bool VisitBinCmp(const BinaryOperator *E); }; } @@ -8215,10 +8223,8 @@ /// We handle binary operators that are comma, logical, or that have operands /// with integral or enumeration type. static bool shouldEnqueue(const BinaryOperator *E) { - return E->getOpcode() == BO_Comma || - E->isLogicalOp() || - (E->isRValue() && - E->getType()->isIntegralOrEnumerationType() && + return E->getOpcode() == BO_Comma || E->isLogicalOp() || + (E->isRValue() && E->getType()->isIntegralOrEnumerationType() && E->getLHS()->getType()->isIntegralOrEnumerationType() && E->getRHS()->getType()->isIntegralOrEnumerationType()); } @@ -8497,19 +8503,47 @@ }; } -bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { - // We don't call noteFailure immediately because the assignment happens after - // we evaluate LHS and RHS. - if (!Info.keepEvaluatingAfterFailure() && E->isAssignmentOp()) - return Error(E); +template +static bool +EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, + SuccessCB &&Success, AfterCB &&DoAfter) { + assert(E->isComparisonOp() && "expected comparison operator"); + assert((E->getOpcode() == BO_Cmp || + E->getType()->isIntegralOrEnumerationType()) && + "unsupported binary expression evaluation"); + auto Error = [&](const Expr *E) { + Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr); + return false; + }; - DelayedNoteFailureRAII MaybeNoteFailureLater(Info, E->isAssignmentOp()); - if (DataRecursiveIntBinOpEvaluator::shouldEnqueue(E)) - return DataRecursiveIntBinOpEvaluator(*this, Result).Traverse(E); + using CCR = ComparisonCategoryResult; + bool IsRelational = E->isRelationalOp(); + bool IsEquality = E->isEqualityOp(); + if (E->getOpcode() == BO_Cmp) { + const ComparisonCategoryInfo &CmpInfo = + Info.Ctx.CompCategories.getInfoForType(E->getType()); + IsRelational = CmpInfo.isOrdered(); + IsEquality = CmpInfo.isEquality(); + } QualType LHSTy = E->getLHS()->getType(); QualType RHSTy = E->getRHS()->getType(); + if (LHSTy->isIntegralOrEnumerationType() && + RHSTy->isIntegralOrEnumerationType()) { + APSInt LHS, RHS; + bool LHSOK = EvaluateInteger(E->getLHS(), LHS, Info); + if (!LHSOK && !Info.noteFailure()) + return false; + if (!EvaluateInteger(E->getRHS(), RHS, Info) || !LHSOK) + return false; + if (LHS < RHS) + return Success(CCR::Less, E); + if (LHS > RHS) + return Success(CCR::Greater, E); + return Success(CCR::Equal, E); + } + if (LHSTy->isAnyComplexType() || RHSTy->isAnyComplexType()) { ComplexValue LHS, RHS; bool LHSOK; @@ -8542,30 +8576,13 @@ LHS.getComplexFloatReal().compare(RHS.getComplexFloatReal()); APFloat::cmpResult CR_i = LHS.getComplexFloatImag().compare(RHS.getComplexFloatImag()); - - if (E->getOpcode() == BO_EQ) - return Success((CR_r == APFloat::cmpEqual && - CR_i == APFloat::cmpEqual), E); - else { - assert(E->getOpcode() == BO_NE && - "Invalid complex comparison."); - return Success(((CR_r == APFloat::cmpGreaterThan || - CR_r == APFloat::cmpLessThan || - CR_r == APFloat::cmpUnordered) || - (CR_i == APFloat::cmpGreaterThan || - CR_i == APFloat::cmpLessThan || - CR_i == APFloat::cmpUnordered)), E); - } + bool IsEqual = CR_r == APFloat::cmpEqual && CR_i == APFloat::cmpEqual; + return Success(IsEqual ? CCR::Equal : CCR::Nonequal, E); } else { - if (E->getOpcode() == BO_EQ) - return Success((LHS.getComplexIntReal() == RHS.getComplexIntReal() && - LHS.getComplexIntImag() == RHS.getComplexIntImag()), E); - else { - assert(E->getOpcode() == BO_NE && - "Invalid compex comparison."); - return Success((LHS.getComplexIntReal() != RHS.getComplexIntReal() || - LHS.getComplexIntImag() != RHS.getComplexIntImag()), E); - } + assert(IsEquality && "invalid complex comparison"); + bool IsEqual = LHS.getComplexIntReal() == RHS.getComplexIntReal() && + LHS.getComplexIntImag() == RHS.getComplexIntImag(); + return Success(IsEqual ? CCR::Equal : CCR::Nonequal, E); } } @@ -8580,31 +8597,23 @@ if (!EvaluateFloat(E->getLHS(), LHS, Info) || !LHSOK) return false; - APFloat::cmpResult CR = LHS.compare(RHS); - - switch (E->getOpcode()) { - default: - llvm_unreachable("Invalid binary operator!"); - case BO_LT: - return Success(CR == APFloat::cmpLessThan, E); - case BO_GT: - return Success(CR == APFloat::cmpGreaterThan, E); - case BO_LE: - return Success(CR == APFloat::cmpLessThan || CR == APFloat::cmpEqual, E); - case BO_GE: - return Success(CR == APFloat::cmpGreaterThan || CR == APFloat::cmpEqual, - E); - case BO_EQ: - return Success(CR == APFloat::cmpEqual, E); - case BO_NE: - return Success(CR == APFloat::cmpGreaterThan - || CR == APFloat::cmpLessThan - || CR == APFloat::cmpUnordered, E); + assert(E->isComparisonOp() && "Invalid binary operator!"); + auto GetCmpRes = [&]() { + switch (LHS.compare(RHS)) { + case APFloat::cmpEqual: + return CCR::Equal; + case APFloat::cmpLessThan: + return CCR::Less; + case APFloat::cmpGreaterThan: + return CCR::Greater; + case APFloat::cmpUnordered: + return CCR::Unordered; } + }; + return Success(GetCmpRes(), E); } if (LHSTy->isPointerType() && RHSTy->isPointerType()) { - if (E->getOpcode() == BO_Sub || E->isComparisonOp()) { LValue LHSValue, RHSValue; bool LHSOK = EvaluatePointer(E->getLHS(), LHSValue, Info); @@ -8617,27 +8626,9 @@ // Reject differing bases from the normal codepath; we special-case // comparisons to null. if (!HasSameBase(LHSValue, RHSValue)) { - if (E->getOpcode() == BO_Sub) { - // Handle &&A - &&B. - if (!LHSValue.Offset.isZero() || !RHSValue.Offset.isZero()) - return Error(E); - const Expr *LHSExpr = LHSValue.Base.dyn_cast(); - const Expr *RHSExpr = RHSValue.Base.dyn_cast(); - if (!LHSExpr || !RHSExpr) - return Error(E); - const AddrLabelExpr *LHSAddrExpr = dyn_cast(LHSExpr); - const AddrLabelExpr *RHSAddrExpr = dyn_cast(RHSExpr); - if (!LHSAddrExpr || !RHSAddrExpr) - return Error(E); - // Make sure both labels come from the same function. - if (LHSAddrExpr->getLabel()->getDeclContext() != - RHSAddrExpr->getLabel()->getDeclContext()) - return Error(E); - return Success(APValue(LHSAddrExpr, RHSAddrExpr), E); - } // Inequalities and subtractions between unrelated pointers have // unspecified or undefined behavior. - if (!E->isEqualityOp()) + if (!IsEquality) return Error(E); // A constant address may compare equal to the address of a symbol. // The one exception is that address of an object cannot compare equal @@ -8668,8 +8659,7 @@ if ((RHSValue.Base && isZeroSized(LHSValue)) || (LHSValue.Base && isZeroSized(RHSValue))) return Error(E); - // Pointers with different bases cannot represent the same object. - return Success(E->getOpcode() == BO_NE, E); + return Success(CCR::Nonequal, E); } const CharUnits &LHSOffset = LHSValue.getLValueOffset(); @@ -8678,55 +8668,6 @@ SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator(); SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator(); - if (E->getOpcode() == BO_Sub) { - // C++11 [expr.add]p6: - // Unless both pointers point to elements of the same array object, or - // one past the last element of the array object, the behavior is - // undefined. - if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && - !AreElementsOfSameArray(getType(LHSValue.Base), - LHSDesignator, RHSDesignator)) - CCEDiag(E, diag::note_constexpr_pointer_subtraction_not_same_array); - - QualType Type = E->getLHS()->getType(); - QualType ElementType = Type->getAs()->getPointeeType(); - - CharUnits ElementSize; - if (!HandleSizeof(Info, E->getExprLoc(), ElementType, ElementSize)) - return false; - - // As an extension, a type may have zero size (empty struct or union in - // C, array of zero length). Pointer subtraction in such cases has - // undefined behavior, so is not constant. - if (ElementSize.isZero()) { - Info.FFDiag(E, diag::note_constexpr_pointer_subtraction_zero_size) - << ElementType; - return false; - } - - // FIXME: LLVM and GCC both compute LHSOffset - RHSOffset at runtime, - // and produce incorrect results when it overflows. Such behavior - // appears to be non-conforming, but is common, so perhaps we should - // assume the standard intended for such cases to be undefined behavior - // and check for them. - - // Compute (LHSOffset - RHSOffset) / Size carefully, checking for - // overflow in the final conversion to ptrdiff_t. - APSInt LHS( - llvm::APInt(65, (int64_t)LHSOffset.getQuantity(), true), false); - APSInt RHS( - llvm::APInt(65, (int64_t)RHSOffset.getQuantity(), true), false); - APSInt ElemSize( - llvm::APInt(65, (int64_t)ElementSize.getQuantity(), true), false); - APSInt TrueResult = (LHS - RHS) / ElemSize; - APSInt Result = TrueResult.trunc(Info.Ctx.getIntWidth(E->getType())); - - if (Result.extend(65) != TrueResult && - !HandleOverflow(Info, E, TrueResult, E->getType())) - return false; - return Success(Result, E); - } - // C++11 [expr.rel]p3: // Pointers to void (after pointer conversions) can be compared, with a // result defined as follows: If both pointers represent the same @@ -8734,9 +8675,8 @@ // operator is <= or >= and false otherwise; otherwise the result is // unspecified. // We interpret this as applying to pointers to *cv* void. - if (LHSTy->isVoidPointerType() && LHSOffset != RHSOffset && - E->isRelationalOp()) - CCEDiag(E, diag::note_constexpr_void_comparison); + if (LHSTy->isVoidPointerType() && LHSOffset != RHSOffset && IsRelational) + Info.CCEDiag(E, diag::note_constexpr_void_comparison); // C++11 [expr.rel]p2: // - If two pointers point to non-static data members of the same object, @@ -8746,12 +8686,10 @@ // not a union. // [...] // - Otherwise pointer comparisons are unspecified. - if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && - E->isRelationalOp()) { + if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && IsRelational) { bool WasArrayIndex; - unsigned Mismatch = - FindDesignatorMismatch(getType(LHSValue.Base), LHSDesignator, - RHSDesignator, WasArrayIndex); + unsigned Mismatch = FindDesignatorMismatch( + getType(LHSValue.Base), LHSDesignator, RHSDesignator, WasArrayIndex); // At the point where the designators diverge, the comparison has a // specified value if: // - we are comparing array indices @@ -8763,18 +8701,19 @@ const FieldDecl *LF = getAsField(LHSDesignator.Entries[Mismatch]); const FieldDecl *RF = getAsField(RHSDesignator.Entries[Mismatch]); if (!LF && !RF) - CCEDiag(E, diag::note_constexpr_pointer_comparison_base_classes); + Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_classes); else if (!LF) - CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) + Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) << getAsBaseClass(LHSDesignator.Entries[Mismatch]) << RF->getParent() << RF; else if (!RF) - CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) + Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) << getAsBaseClass(RHSDesignator.Entries[Mismatch]) << LF->getParent() << LF; else if (!LF->getParent()->isUnion() && LF->getAccess() != RF->getAccess()) - CCEDiag(E, diag::note_constexpr_pointer_comparison_differing_access) + Info.CCEDiag(E, + diag::note_constexpr_pointer_comparison_differing_access) << LF << LF->getAccess() << RF << RF->getAccess() << LF->getParent(); } @@ -8793,7 +8732,7 @@ // If there is a base and this is a relational operator, we can only // compare pointers within the object in question; otherwise, the result // depends on where the object is located in memory. - if (!LHSValue.Base.isNull() && E->isRelationalOp()) { + if (!LHSValue.Base.isNull() && IsRelational) { QualType BaseTy = getType(LHSValue.Base); if (BaseTy->isIncompleteType()) return Error(E); @@ -8803,20 +8742,15 @@ return Error(E); } - switch (E->getOpcode()) { - default: llvm_unreachable("missing comparison operator"); - case BO_LT: return Success(CompareLHS < CompareRHS, E); - case BO_GT: return Success(CompareLHS > CompareRHS, E); - case BO_LE: return Success(CompareLHS <= CompareRHS, E); - case BO_GE: return Success(CompareLHS >= CompareRHS, E); - case BO_EQ: return Success(CompareLHS == CompareRHS, E); - case BO_NE: return Success(CompareLHS != CompareRHS, E); - } - } + if (CompareLHS < CompareRHS) + return Success(CCR::Less, E); + if (CompareLHS > CompareRHS) + return Success(CCR::Greater, E); + return Success(CCR::Equal, E); } if (LHSTy->isMemberPointerType()) { - assert(E->isEqualityOp() && "unexpected member pointer operation"); + assert(IsEquality && "unexpected member pointer operation"); assert(RHSTy->isMemberPointerType() && "invalid comparison"); MemberPtr LHSValue, RHSValue; @@ -8833,24 +8767,24 @@ // null, they compare unequal. if (!LHSValue.getDecl() || !RHSValue.getDecl()) { bool Equal = !LHSValue.getDecl() && !RHSValue.getDecl(); - return Success(E->getOpcode() == BO_EQ ? Equal : !Equal, E); + return Success(Equal ? CCR::Equal : CCR::Nonequal, E); } // Otherwise if either is a pointer to a virtual member function, the // result is unspecified. if (const CXXMethodDecl *MD = dyn_cast(LHSValue.getDecl())) if (MD->isVirtual()) - CCEDiag(E, diag::note_constexpr_compare_virtual_mem_ptr) << MD; + Info.CCEDiag(E, diag::note_constexpr_compare_virtual_mem_ptr) << MD; if (const CXXMethodDecl *MD = dyn_cast(RHSValue.getDecl())) if (MD->isVirtual()) - CCEDiag(E, diag::note_constexpr_compare_virtual_mem_ptr) << MD; + Info.CCEDiag(E, diag::note_constexpr_compare_virtual_mem_ptr) << MD; // Otherwise they compare equal if and only if they would refer to the // same member of the same most derived object or the same subobject if // they were dereferenced with a hypothetical object of the associated // class type. bool Equal = LHSValue == RHSValue; - return Success(E->getOpcode() == BO_EQ ? Equal : !Equal, E); + return Success(Equal ? CCR::Equal : CCR::Nonequal, E); } if (LHSTy->isNullPtrType()) { @@ -8859,14 +8793,163 @@ // C++11 [expr.rel]p4, [expr.eq]p3: If two operands of type std::nullptr_t // are compared, the result is true of the operator is <=, >= or ==, and // false otherwise. - BinaryOperator::Opcode Opcode = E->getOpcode(); - return Success(Opcode == BO_EQ || Opcode == BO_LE || Opcode == BO_GE, E); + return Success(CCR::Equal, E); } - assert((!LHSTy->isIntegralOrEnumerationType() || - !RHSTy->isIntegralOrEnumerationType()) && + return DoAfter(); +} + +bool RecordExprEvaluator::VisitBinCmp(const BinaryOperator *E) { + if (!CheckLiteralType(Info, E)) + return false; + + auto OnSuccess = [&](ComparisonCategoryResult ResKind, + const BinaryOperator *E) { + // Evaluation succeeded. Lookup the information for the comparison category + // type and fetch the VarDecl for the result. + const ComparisonCategoryInfo &CmpInfo = + Info.Ctx.CompCategories.getInfoForType(E->getType()); + const VarDecl *VD = CmpInfo.getResultDecl(CmpInfo.makeWeakResult(ResKind)); + assert(!VD->hasLocalStorage() && !VD->getType()->isReferenceType()); + // Check and evaluate the result as a constant expression. + LValue LV; + LV.set(VD); + if (!handleLValueToRValueConversion(Info, E, E->getType(), LV, Result)) + return false; + return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result); + }; + return EvaluateComparisonBinaryOperator(Info, E, OnSuccess, [&]() { + return ExprEvaluatorBaseTy::VisitBinaryOperator(E); + }); +} + +bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { + // We don't call noteFailure immediately because the assignment happens after + // we evaluate LHS and RHS. + if (!Info.keepEvaluatingAfterFailure() && E->isAssignmentOp()) + return Error(E); + + DelayedNoteFailureRAII MaybeNoteFailureLater(Info, E->isAssignmentOp()); + if (DataRecursiveIntBinOpEvaluator::shouldEnqueue(E)) + return DataRecursiveIntBinOpEvaluator(*this, Result).Traverse(E); + + assert((!E->getLHS()->getType()->isIntegralOrEnumerationType() || + !E->getRHS()->getType()->isIntegralOrEnumerationType()) && "DataRecursiveIntBinOpEvaluator should have handled integral types"); - // We can't continue from here for non-integral types. + + if (E->isComparisonOp()) { + // Evaluate builtin binary comparisons by evaluating them as C++2a three-way + // comparisons and then translating the result. + auto OnSuccess = [&](ComparisonCategoryResult ResKind, + const BinaryOperator *E) { + using CCR = ComparisonCategoryResult; + bool IsEqual = ResKind == CCR::Equal, + IsLess = ResKind == CCR::Less, + IsGreater = ResKind == CCR::Greater; + auto Op = E->getOpcode(); + switch (Op) { + default: + llvm_unreachable("unsupported binary operator"); + case BO_EQ: + case BO_NE: + return Success(IsEqual == (Op == BO_EQ), E); + case BO_LT: return Success(IsLess, E); + case BO_GT: return Success(IsGreater, E); + case BO_LE: return Success(IsEqual || IsLess, E); + case BO_GE: return Success(IsEqual || IsGreater, E); + } + }; + return EvaluateComparisonBinaryOperator(Info, E, OnSuccess, [&]() { + return ExprEvaluatorBaseTy::VisitBinaryOperator(E); + }); + } + + QualType LHSTy = E->getLHS()->getType(); + QualType RHSTy = E->getRHS()->getType(); + + if (LHSTy->isPointerType() && RHSTy->isPointerType() && + E->getOpcode() == BO_Sub) { + LValue LHSValue, RHSValue; + + bool LHSOK = EvaluatePointer(E->getLHS(), LHSValue, Info); + if (!LHSOK && !Info.noteFailure()) + return false; + + if (!EvaluatePointer(E->getRHS(), RHSValue, Info) || !LHSOK) + return false; + + // Reject differing bases from the normal codepath; we special-case + // comparisons to null. + if (!HasSameBase(LHSValue, RHSValue)) { + // Handle &&A - &&B. + if (!LHSValue.Offset.isZero() || !RHSValue.Offset.isZero()) + return Error(E); + const Expr *LHSExpr = LHSValue.Base.dyn_cast(); + const Expr *RHSExpr = RHSValue.Base.dyn_cast(); + if (!LHSExpr || !RHSExpr) + return Error(E); + const AddrLabelExpr *LHSAddrExpr = dyn_cast(LHSExpr); + const AddrLabelExpr *RHSAddrExpr = dyn_cast(RHSExpr); + if (!LHSAddrExpr || !RHSAddrExpr) + return Error(E); + // Make sure both labels come from the same function. + if (LHSAddrExpr->getLabel()->getDeclContext() != + RHSAddrExpr->getLabel()->getDeclContext()) + return Error(E); + return Success(APValue(LHSAddrExpr, RHSAddrExpr), E); + } + const CharUnits &LHSOffset = LHSValue.getLValueOffset(); + const CharUnits &RHSOffset = RHSValue.getLValueOffset(); + + SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator(); + SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator(); + + // C++11 [expr.add]p6: + // Unless both pointers point to elements of the same array object, or + // one past the last element of the array object, the behavior is + // undefined. + if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && + !AreElementsOfSameArray(getType(LHSValue.Base), LHSDesignator, + RHSDesignator)) + Info.CCEDiag(E, diag::note_constexpr_pointer_subtraction_not_same_array); + + QualType Type = E->getLHS()->getType(); + QualType ElementType = Type->getAs()->getPointeeType(); + + CharUnits ElementSize; + if (!HandleSizeof(Info, E->getExprLoc(), ElementType, ElementSize)) + return false; + + // As an extension, a type may have zero size (empty struct or union in + // C, array of zero length). Pointer subtraction in such cases has + // undefined behavior, so is not constant. + if (ElementSize.isZero()) { + Info.FFDiag(E, diag::note_constexpr_pointer_subtraction_zero_size) + << ElementType; + return false; + } + + // FIXME: LLVM and GCC both compute LHSOffset - RHSOffset at runtime, + // and produce incorrect results when it overflows. Such behavior + // appears to be non-conforming, but is common, so perhaps we should + // assume the standard intended for such cases to be undefined behavior + // and check for them. + + // Compute (LHSOffset - RHSOffset) / Size carefully, checking for + // overflow in the final conversion to ptrdiff_t. + APSInt LHS(llvm::APInt(65, (int64_t)LHSOffset.getQuantity(), true), false); + APSInt RHS(llvm::APInt(65, (int64_t)RHSOffset.getQuantity(), true), false); + APSInt ElemSize(llvm::APInt(65, (int64_t)ElementSize.getQuantity(), true), + false); + APSInt TrueResult = (LHS - RHS) / ElemSize; + APSInt Result = TrueResult.trunc(Info.Ctx.getIntWidth(E->getType())); + + if (Result.extend(65) != TrueResult && + !HandleOverflow(Info, E, TrueResult, E->getType())) + return false; + return Success(Result, E); + } + return ExprEvaluatorBaseTy::VisitBinaryOperator(E); } @@ -10609,7 +10692,6 @@ case BO_AndAssign: case BO_XorAssign: case BO_OrAssign: - case BO_Cmp: // FIXME: Re-enable once we can evaluate this. // C99 6.6/3 allows assignments within unevaluated subexpressions of // constant expressions, but they can never be ICEs because an ICE cannot // contain an lvalue operand. @@ -10631,7 +10713,8 @@ case BO_And: case BO_Xor: case BO_Or: - case BO_Comma: { + case BO_Comma: + case BO_Cmp: { ICEDiag LHSResult = CheckICE(Exp->getLHS(), Ctx); ICEDiag RHSResult = CheckICE(Exp->getRHS(), Ctx); if (Exp->getOpcode() == BO_Div || @@ -10768,6 +10851,8 @@ case Expr::ChooseExprClass: { return CheckICE(cast(E)->getChosenSubExpr(), Ctx); } + case Expr::CXXRewrittenOperatorClass: + return CheckICE(cast(E)->getRewrittenExpr(), Ctx); } llvm_unreachable("Invalid StmtClass!"); Index: lib/AST/ItaniumMangle.cpp =================================================================== --- lib/AST/ItaniumMangle.cpp +++ lib/AST/ItaniumMangle.cpp @@ -3551,6 +3551,11 @@ mangleExpression(cast(E)->getSubExpr(), Arity); break; + case Expr::CXXRewrittenOperatorClass: + // FIXME(EricWF): Is this correct? + mangleExpression(cast(E)->getRewrittenExpr(), Arity); + break; + case Expr::SubstNonTypeTemplateParmExprClass: mangleExpression(cast(E)->getReplacement(), Arity); Index: lib/AST/StmtPrinter.cpp =================================================================== --- lib/AST/StmtPrinter.cpp +++ lib/AST/StmtPrinter.cpp @@ -2588,6 +2588,15 @@ OS << ")"; } +void StmtPrinter::VisitCXXRewrittenOperator(CXXRewrittenOperator *E) { + // FIXME(EricWF): Are there ever cases where we want to display the rewritten + // code? For example when producing diagnostics on implicitly generated + // expressions? + Visit(E->getOrigLHS()); + OS << " " << BinaryOperator::getOpcodeStr(E->getOpcode()) << " "; + Visit(E->getOrigRHS()); +} + // C++ Coroutines TS void StmtPrinter::VisitCoroutineBodyStmt(CoroutineBodyStmt *S) { Index: lib/AST/StmtProfile.cpp =================================================================== --- lib/AST/StmtProfile.cpp +++ lib/AST/StmtProfile.cpp @@ -1816,6 +1816,11 @@ ID.AddInteger(S->getOperator()); } +void StmtProfiler::VisitCXXRewrittenOperator(const CXXRewrittenOperator *S) { + VisitExpr(S); + ID.AddInteger(S->getKind()); +} + void StmtProfiler::VisitCoroutineBodyStmt(const CoroutineBodyStmt *S) { VisitStmt(S); } Index: lib/CodeGen/CGExprAgg.cpp =================================================================== --- lib/CodeGen/CGExprAgg.cpp +++ lib/CodeGen/CGExprAgg.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "CodeGenFunction.h" +#include "CGCXXABI.h" #include "CGObjCRuntime.h" #include "CodeGenModule.h" #include "ConstantEmitter.h" @@ -145,6 +146,7 @@ void VisitPointerToDataMemberBinaryOperator(const BinaryOperator *BO); void VisitBinAssign(const BinaryOperator *E); void VisitBinComma(const BinaryOperator *E); + void VisitBinCmp(const BinaryOperator *E); void VisitObjCMessageExpr(ObjCMessageExpr *E); void VisitObjCIvarRefExpr(ObjCIvarRefExpr *E) { @@ -879,6 +881,127 @@ CGF.EmitCompoundStmt(*E->getSubStmt(), true, Dest); } +enum CompareKind { + CK_Less, + CK_Greater, + CK_Equal, +}; + +static llvm::Value *EmitCompare(CGBuilderTy &Builder, CodeGenFunction &CGF, + const BinaryOperator *E, llvm::Value *LHS, + llvm::Value *RHS, CompareKind Kind) { + QualType ArgTy = E->getLHS()->getType(); + if (const auto *MPT = ArgTy->getAs()) { + assert(Kind == CK_Equal && + "member pointers may only be compared for equality"); + return CGF.CGM.getCXXABI().EmitMemberPointerComparison( + CGF, LHS, RHS, MPT, /*IsInequality*/ false); + } + + // Compute the comparison instructions for the specified comparison kind. + struct CmpInstInfo { + const char *Name; + llvm::CmpInst::Predicate FCmp; + llvm::CmpInst::Predicate SCmp; + llvm::CmpInst::Predicate UCmp; + }; + CmpInstInfo InstInfo = [&]() -> CmpInstInfo { + using FI = llvm::FCmpInst; + using II = llvm::ICmpInst; + switch (Kind) { + case CK_Less: + return {"cmp.lt", FI::FCMP_OLT, II::ICMP_SLT, II::ICMP_ULT}; + case CK_Greater: + return {"cmp.gt", FI::FCMP_OGT, II::ICMP_SGT, II::ICMP_UGT}; + case CK_Equal: + return {"cmp.eq", FI::FCMP_OEQ, II::ICMP_EQ, II::ICMP_EQ}; + } + }(); + + if (ArgTy->isRealFloatingType()) + return Builder.CreateFCmp(InstInfo.FCmp, LHS, RHS, InstInfo.Name); + if (ArgTy->isIntegralOrEnumerationType() || ArgTy->isPointerType()) { + auto Inst = + ArgTy->hasSignedIntegerRepresentation() ? InstInfo.SCmp : InstInfo.UCmp; + return Builder.CreateICmp(Inst, LHS, RHS, InstInfo.Name); + } + + llvm_unreachable("unsupported aggregate binary expression should have " + "already been handled"); +} + +void AggExprEmitter::VisitBinCmp(const BinaryOperator *E) { + using llvm::BasicBlock; + using llvm::PHINode; + using llvm::Value; + assert(CGF.getContext().hasSameType(E->getLHS()->getType(), + E->getRHS()->getType())); + const ComparisonCategoryInfo &CmpInfo = + CGF.getContext().CompCategories.getInfoForType(E->getType()); + + QualType ArgTy = E->getLHS()->getType(); + + // TODO: Handle comparing these types. + if (ArgTy->isAnyComplexType()) + return CGF.ErrorUnsupported( + E, "aggregate three-way comparison with complex arguments"); + if (ArgTy->isVectorType()) + return CGF.ErrorUnsupported( + E, "aggregate three-way comparison with vector arguments"); + if (!ArgTy->isIntegralOrEnumerationType() && !ArgTy->isRealFloatingType() && + !ArgTy->isPointerType() && !ArgTy->isMemberPointerType()) + return CGF.ErrorUnsupported(E, "aggregate three-way comparison"); + + Value *LHS, *RHS; + switch (CGF.getEvaluationKind(ArgTy)) { + case TEK_Scalar: + LHS = CGF.EmitScalarExpr(E->getLHS()); + RHS = CGF.EmitScalarExpr(E->getRHS()); + break; + case TEK_Aggregate: + LHS = CGF.EmitAnyExpr(E->getLHS()).getAggregatePointer(); + RHS = CGF.EmitAnyExpr(E->getRHS()).getAggregatePointer(); + break; + case TEK_Complex: + llvm_unreachable( + "unsupported complex expressions should have already been handled"); + } + + auto EmitCmpRes = [&](const VarDecl *VD) { + return CGF.CGM.GetAddrOfGlobalVar(VD); + }; + auto EmitCmp = [&](CompareKind K) { + return EmitCompare(Builder, CGF, E, LHS, RHS, K); + }; + Value *Select; + if (CmpInfo.isEquality()) { + Select = Builder.CreateSelect( + EmitCmp(CK_Equal), EmitCmpRes(CmpInfo.getEqualOrEquiv()), + EmitCmpRes(CmpInfo.getNonequalOrNonequiv()), "sel.eq"); + } else if (!CmpInfo.isPartial()) { + Value *SelectOne = + Builder.CreateSelect(EmitCmp(CK_Less), EmitCmpRes(CmpInfo.getLess()), + EmitCmpRes(CmpInfo.getGreater()), "sel.lt"); + Select = Builder.CreateSelect(EmitCmp(CK_Equal), + EmitCmpRes(CmpInfo.getEqualOrEquiv()), + SelectOne, "sel.eq"); + } else { + Value *SelectEq = Builder.CreateSelect( + EmitCmp(CK_Equal), EmitCmpRes(CmpInfo.getEqualOrEquiv()), + EmitCmpRes(CmpInfo.getUnordered()), "sel.eq"); + Value *SelectGT = Builder.CreateSelect(EmitCmp(CK_Greater), + EmitCmpRes(CmpInfo.getGreater()), + SelectEq, "sel.gt"); + Select = Builder.CreateSelect( + EmitCmp(CK_Less), EmitCmpRes(CmpInfo.getLess()), SelectGT, "sel.lt"); + } + + // TODO Is it worthwhile to try to generate a select between the comparison + // result values rather than their addresses? + return EmitFinalDestCopy( + E->getType(), CGF.MakeNaturalAlignAddrLValue(Select, E->getType())); +} + void AggExprEmitter::VisitBinaryOperator(const BinaryOperator *E) { if (E->getOpcode() == BO_PtrMemD || E->getOpcode() == BO_PtrMemI) VisitPointerToDataMemberBinaryOperator(E); Index: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -541,6 +541,10 @@ return EmitScalarPrePostIncDec(E, LV, true, true); } + Value *VisitCXXRewrittenOperator(const CXXRewrittenOperator *E) { + return Visit(E->getRewrittenExpr()); + } + llvm::Value *EmitIncDecConsiderOverflowBehavior(const UnaryOperator *E, llvm::Value *InVal, bool IsInc); Index: lib/Sema/SemaCUDA.cpp =================================================================== --- lib/Sema/SemaCUDA.cpp +++ lib/Sema/SemaCUDA.cpp @@ -303,10 +303,11 @@ /* ConstThis */ false, /* VolatileThis */ false); - if (!SMOR.getMethod()) + if (!SMOR.getFunction()) continue; - CUDAFunctionTarget BaseMethodTarget = IdentifyCUDATarget(SMOR.getMethod()); + CUDAFunctionTarget BaseMethodTarget = + IdentifyCUDATarget(SMOR.getFunction()); if (!InferredTarget.hasValue()) { InferredTarget = BaseMethodTarget; } else { @@ -346,11 +347,11 @@ /* ConstThis */ false, /* VolatileThis */ false); - if (!SMOR.getMethod()) + if (!SMOR.getFunction()) continue; CUDAFunctionTarget FieldMethodTarget = - IdentifyCUDATarget(SMOR.getMethod()); + IdentifyCUDATarget(SMOR.getFunction()); if (!InferredTarget.hasValue()) { InferredTarget = FieldMethodTarget; } else { Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -2799,24 +2799,25 @@ } // end anonymous namespace /// getSpecialMember - get the special member enum for a method. -Sema::CXXSpecialMember Sema::getSpecialMember(const CXXMethodDecl *MD) { +Sema::CXXSpecialMember Sema::getSpecialMember(const FunctionDecl *FD, + bool IsExplicitlyDefault) { + if (FD->isDefaultComparisonOperator(IsExplicitlyDefault)) + return Sema::CXXComparisonOperator; + if (const auto *MD = dyn_cast(FD)) { if (const CXXConstructorDecl *Ctor = dyn_cast(MD)) { if (Ctor->isDefaultConstructor()) return Sema::CXXDefaultConstructor; - if (Ctor->isCopyConstructor()) return Sema::CXXCopyConstructor; - if (Ctor->isMoveConstructor()) return Sema::CXXMoveConstructor; - } else if (isa(MD)) { + } else if (isa(MD)) return Sema::CXXDestructor; - } else if (MD->isCopyAssignmentOperator()) { + else if (MD->isCopyAssignmentOperator()) return Sema::CXXCopyAssignment; - } else if (MD->isMoveAssignmentOperator()) { + else if (MD->isMoveAssignmentOperator()) return Sema::CXXMoveAssignment; } - return Sema::CXXInvalid; } Index: lib/Sema/SemaDeclCXX.cpp =================================================================== --- lib/Sema/SemaDeclCXX.cpp +++ lib/Sema/SemaDeclCXX.cpp @@ -17,6 +17,7 @@ #include "clang/AST/ASTMutationListener.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/CharUnits.h" +#include "clang/AST/ComparisonCategories.h" #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/RecordLayout.h" @@ -148,15 +149,13 @@ } } -void -Sema::ImplicitExceptionSpecification::CalledDecl(SourceLocation CallLoc, - const CXXMethodDecl *Method) { +void Sema::ImplicitExceptionSpecification::CalledDecl(SourceLocation CallLoc, + const FunctionDecl *FD) { // If we have an MSAny spec already, don't bother. - if (!Method || ComputedEST == EST_MSAny) + if (!FD || ComputedEST == EST_MSAny) return; - const FunctionProtoType *Proto - = Method->getType()->getAs(); + const FunctionProtoType *Proto = FD->getType()->getAs(); Proto = Self->ResolveExceptionSpec(CallLoc, Proto); if (!Proto) return; @@ -167,7 +166,7 @@ if (ComputedEST == EST_None) return; - if (EST == EST_None && Method->hasAttr()) + if (EST == EST_None && FD->hasAttr()) EST = EST_BasicNoexcept; switch(EST) { @@ -5764,27 +5763,30 @@ } } -static void DefineImplicitSpecialMember(Sema &S, CXXMethodDecl *MD, +static void DefineImplicitSpecialMember(Sema &S, FunctionDecl *FD, SourceLocation DefaultLoc) { - switch (S.getSpecialMember(MD)) { + switch (S.getSpecialMember(FD)) { case Sema::CXXDefaultConstructor: S.DefineImplicitDefaultConstructor(DefaultLoc, - cast(MD)); + cast(FD)); break; case Sema::CXXCopyConstructor: - S.DefineImplicitCopyConstructor(DefaultLoc, cast(MD)); + S.DefineImplicitCopyConstructor(DefaultLoc, cast(FD)); break; case Sema::CXXCopyAssignment: - S.DefineImplicitCopyAssignment(DefaultLoc, MD); + S.DefineImplicitCopyAssignment(DefaultLoc, cast(FD)); break; case Sema::CXXDestructor: - S.DefineImplicitDestructor(DefaultLoc, cast(MD)); + S.DefineImplicitDestructor(DefaultLoc, cast(FD)); break; case Sema::CXXMoveConstructor: - S.DefineImplicitMoveConstructor(DefaultLoc, cast(MD)); + S.DefineImplicitMoveConstructor(DefaultLoc, cast(FD)); break; case Sema::CXXMoveAssignment: - S.DefineImplicitMoveAssignment(DefaultLoc, MD); + S.DefineImplicitMoveAssignment(DefaultLoc, cast(FD)); + break; + case Sema::CXXComparisonOperator: + S.DefineDefaultedComparisonOperator(DefaultLoc, FD); break; case Sema::CXXInvalid: llvm_unreachable("Invalid special member."); @@ -6005,7 +6007,7 @@ bool HasMethodWithOverrideControl = false, HasOverridingMethodWithoutOverrideControl = false; if (!Record->isDependentType()) { - for (auto *M : Record->methods()) { + for (CXXMethodDecl *M : Record->methods()) { // See if a method overloads virtual methods in a base // class without overriding any. if (!M->isStatic()) @@ -6016,7 +6018,7 @@ HasOverridingMethodWithoutOverrideControl = true; // Check whether the explicitly-defaulted special members are valid. if (!M->isInvalidDecl() && M->isExplicitlyDefaulted()) - CheckExplicitlyDefaultedSpecialMember(M); + CheckExplicitlyDefaultedMember(M); // For an explicitly defaulted or deleted special member, we defer // determining triviality until the class is complete. That time is now! @@ -6036,7 +6038,8 @@ // Set triviality for the purpose of calls if this is a user-provided // copy/move constructor or destructor. if ((CSM == CXXCopyConstructor || CSM == CXXMoveConstructor || - CSM == CXXDestructor) && M->isUserProvided()) { + CSM == CXXDestructor) && + M->isUserProvided()) { M->setTrivialForCall(HasTrivialABI); Record->setTrivialForCallFlags(M); } @@ -6097,15 +6100,23 @@ /// Look up the special member function that would be called by a special /// member function for a subobject of class type. /// +/// \param EvaluatingClass The class type containing the subobject. /// \param Class The class type of the subobject. /// \param CSM The kind of special member function. /// \param FieldQuals If the subobject is a field, its cv-qualifiers. /// \param ConstRHS True if this is a copy operation with a const object /// on its RHS, that is, if the argument to the outer special member /// function is 'const' and this is not a field marked 'mutable'. -static Sema::SpecialMemberOverloadResult lookupCallFromSpecialMember( - Sema &S, CXXRecordDecl *Class, Sema::CXXSpecialMember CSM, +static Sema::SpecialMemberOverloadResult +lookupCallFromSpecialMember(Sema &S, CXXRecordDecl *EvaluatingClass, + CXXRecordDecl *Class, Sema::CXXSpecialMember CSM, unsigned FieldQuals, bool ConstRHS) { + if (CSM == Sema::CXXComparisonOperator) { + QualType ArgTy(Class->getTypeForDecl(), FieldQuals); + // FIXME(EricWF): Get a source location + return S.LookupThreeWayComparison(EvaluatingClass, ArgTy, + Class->getLocation()); + } unsigned LHSQuals = 0; if (CSM == Sema::CXXCopyAssignment || CSM == Sema::CXXMoveAssignment) LHSQuals = FieldQuals; @@ -6208,9 +6219,9 @@ /// Is the special member function which would be selected to perform the /// specified operation on the specified class type a constexpr constructor? static bool -specialMemberIsConstexpr(Sema &S, CXXRecordDecl *ClassDecl, - Sema::CXXSpecialMember CSM, unsigned Quals, - bool ConstRHS, +specialMemberIsConstexpr(Sema &S, CXXRecordDecl *EvaluatingClass, + CXXRecordDecl *ClassDecl, Sema::CXXSpecialMember CSM, + unsigned Quals, bool ConstRHS, CXXConstructorDecl *InheritedCtor = nullptr, Sema::InheritedConstructorInfo *Inherited = nullptr) { // If we're inheriting a constructor, see if we need to call it for this base @@ -6226,13 +6237,132 @@ if (CSM == Sema::CXXDefaultConstructor) return ClassDecl->hasConstexprDefaultConstructor(); - Sema::SpecialMemberOverloadResult SMOR = - lookupCallFromSpecialMember(S, ClassDecl, CSM, Quals, ConstRHS); - if (!SMOR.getMethod()) + Sema::SpecialMemberOverloadResult SMOR = lookupCallFromSpecialMember( + S, EvaluatingClass, ClassDecl, CSM, Quals, ConstRHS); + if (!SMOR.getFunction()) // A constructor we wouldn't select can't be "involved in initializing" // anything. return true; - return SMOR.getMethod()->isConstexpr(); + return SMOR.getFunction()->isConstexpr(); +} + +static bool specialMemberIsConstexpr( + Sema &S, CXXRecordDecl *ClassDecl, Sema::CXXSpecialMember CSM, + unsigned Quals, bool ConstRHS, CXXConstructorDecl *InheritedCtor = nullptr, + Sema::InheritedConstructorInfo *Inherited = nullptr) { + return specialMemberIsConstexpr(S, nullptr, ClassDecl, CSM, Quals, ConstRHS, + InheritedCtor, Inherited); +} + +struct DefaultedComparisonInfo { + llvm::SmallVector ReturnTypes; + QualType CommonCategoryType; + bool ShouldDelete = false; + bool IsConstexpr = true; +}; + +static bool deduceBuiltinComparisonReturnType(Sema &S, QualType ArgType, + SourceLocation Loc, + DefaultedComparisonInfo &Info) { + using CCT = ComparisonCategoryType; + ComparisonCategoryType ReturnKind; + if (ArgType->hasFloatingRepresentation()) { + ReturnKind = CCT::PartialOrdering; + } else if (ArgType->isIntegralOrEnumerationType()) { + ReturnKind = CCT::StrongOrdering; + } else if (ArgType->isMemberPointerType()) { + ReturnKind = CCT::StrongEquality; + } else if (ArgType->isPointerType()) { + ReturnKind = CCT::StrongOrdering; + } else { + // Not a type supported for builtin comparisons. (likely an array type) + Info.ShouldDelete = true; + return true; + } + // Require that the header has already been included and that + // computed comparison category declaration has been found. + QualType CompType = S.CheckComparisonCategoryType(ReturnKind, Loc); + if (CompType.isNull()) { + Info.ShouldDelete = true; + return true; + } + Info.ReturnTypes.push_back(CompType); + return false; +} + +static DefaultedComparisonInfo +getDefaultedComparisonInfo(Sema &S, FunctionDecl *CompareOperator, + CXXRecordDecl *ClassDecl, SourceLocation Loc) { + using OvlResult = Sema::SpecialMemberOverloadResult; + using ResultKind = OvlResult::Kind; + DefaultedComparisonInfo Info; + Info.CommonCategoryType = S.Context.VoidTy; + + auto CheckOverload = [&](QualType ArgType) { + // FIXME(EricWF): Get and pass a better source location here. + OvlResult Res = S.LookupThreeWayComparison(ClassDecl, ArgType, Loc); + switch (Res.getKind()) { + case ResultKind::Success: { + if (Res.hasBuiltin()) { + if (deduceBuiltinComparisonReturnType(S, Res.getBuiltinArgType(), Loc, + Info)) + return true; + } else { + FunctionDecl *FD = Res.getFunction(); + if (FD->getReturnType()->isUndeducedType()) { + // FIXME(EricWF): Do this correctly? + // And should we be complaining during the initial pass? + if (S.DeduceReturnType(FD, Loc, /*Complain*/ true)) { + Info.ShouldDelete = true; + return true; + } + } + + Info.ReturnTypes.push_back(FD->getReturnType()); + } + return false; + } + case ResultKind::Ambiguous: + case ResultKind::NoMemberOrDeleted: { + return true; + } + } + }; + for (const auto &B : ClassDecl->bases()) { + const RecordType *BaseType = B.getType()->getAs(); + if (!BaseType) + continue; + + CXXRecordDecl *BaseClassDecl = cast(BaseType->getDecl()); + QualType ArgTy(BaseClassDecl->getTypeForDecl(), 0); + if (CheckOverload(ArgTy)) + return Info; + } + + for (const auto *F : ClassDecl->fields()) { + if (F->isInvalidDecl()) + continue; + QualType BaseType = S.Context.getBaseElementType(F->getType()); + // If the field has a reference type, remove it. Expressions can't have + // reference types. + BaseType = BaseType.getNonReferenceType(); + if (CheckOverload(BaseType)) + return Info; + } + + // OK, we have a potentially OK comparison operator. Compute the common + // comparison type, diagnosing errors relating to the lookup of STL + // declarations. + QualType CommonType = S.ComputeCommonComparisonType(Info.ReturnTypes, Loc); + // An error occured. + if (CommonType.isNull()) { + // FIXME: Should we mark this decl as invalid? + CompareOperator->setInvalidDecl(); + return Info; + } + + Info.CommonCategoryType = CommonType; + return Info; } /// Determine whether the specified special member function would be constexpr @@ -6271,7 +6401,8 @@ // In C++1y, we need to perform overload resolution. Ctor = false; break; - + case Sema::CXXComparisonOperator: + break; case Sema::CXXDestructor: case Sema::CXXInvalid: return false; @@ -6328,7 +6459,7 @@ QualType BaseType = S.Context.getBaseElementType(F->getType()); if (const RecordType *RecordTy = BaseType->getAs()) { CXXRecordDecl *FieldRecDecl = cast(RecordTy->getDecl()); - if (!specialMemberIsConstexpr(S, FieldRecDecl, CSM, + if (!specialMemberIsConstexpr(S, ClassDecl, FieldRecDecl, CSM, BaseType.getCVRQualifiers(), ConstArg && !F->isMutable())) return false; @@ -6342,17 +6473,18 @@ } static Sema::ImplicitExceptionSpecification -ComputeDefaultedSpecialMemberExceptionSpec( - Sema &S, SourceLocation Loc, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM, +ComputeDefaultedSpecialMemberExceptionSpec(Sema &S, SourceLocation Loc, + FunctionDecl *FD, + Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI); static Sema::ImplicitExceptionSpecification -computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, CXXMethodDecl *MD) { - auto CSM = S.getSpecialMember(MD); +computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, FunctionDecl *FD) { + auto CSM = S.getSpecialMember(FD); if (CSM != Sema::CXXInvalid) - return ComputeDefaultedSpecialMemberExceptionSpec(S, Loc, MD, CSM, nullptr); + return ComputeDefaultedSpecialMemberExceptionSpec(S, Loc, FD, CSM, nullptr); - auto *CD = cast(MD); + auto *CD = cast(FD); assert(CD->getInheritedConstructor() && "only special members have implicit exception specs"); Sema::InheritedConstructorInfo ICI( @@ -6362,54 +6494,69 @@ } static FunctionProtoType::ExtProtoInfo getImplicitMethodEPI(Sema &S, - CXXMethodDecl *MD) { + FunctionDecl *FD) { FunctionProtoType::ExtProtoInfo EPI; // Build an exception specification pointing back at this member. EPI.ExceptionSpec.Type = EST_Unevaluated; - EPI.ExceptionSpec.SourceDecl = MD; + EPI.ExceptionSpec.SourceDecl = FD; // Set the calling convention to the default for C++ instance methods. - EPI.ExtInfo = EPI.ExtInfo.withCallingConv( - S.Context.getDefaultCallingConvention(/*IsVariadic=*/false, - /*IsCXXMethod=*/true)); + EPI.ExtInfo = + EPI.ExtInfo.withCallingConv(S.Context.getDefaultCallingConvention( + /*IsVariadic=*/false, + /*IsCXXMethod=*/isa(FD))); return EPI; } -void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD) { - const FunctionProtoType *FPT = MD->getType()->castAs(); +void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD) { + const FunctionProtoType *FPT = FD->getType()->castAs(); if (FPT->getExceptionSpecType() != EST_Unevaluated) return; // Evaluate the exception specification. - auto IES = computeImplicitExceptionSpec(*this, Loc, MD); + auto IES = computeImplicitExceptionSpec(*this, Loc, FD); auto ESI = IES.getExceptionSpec(); // Update the type of the special member to use it. - UpdateExceptionSpec(MD, ESI); + UpdateExceptionSpec(FD, ESI); // A user-provided destructor can be defined outside the class. When that // happens, be sure to update the exception specification on both // declarations. const FunctionProtoType *CanonicalFPT = - MD->getCanonicalDecl()->getType()->castAs(); + FD->getCanonicalDecl()->getType()->castAs(); if (CanonicalFPT->getExceptionSpecType() == EST_Unevaluated) - UpdateExceptionSpec(MD->getCanonicalDecl(), ESI); + UpdateExceptionSpec(FD->getCanonicalDecl(), ESI); } -void Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD) { - CXXRecordDecl *RD = MD->getParent(); - CXXSpecialMember CSM = getSpecialMember(MD); +void Sema::CheckExplicitlyDefaultedMember(FunctionDecl *FD) { + CXXSpecialMember CSM = getSpecialMember(FD); - assert(MD->isExplicitlyDefaulted() && CSM != CXXInvalid && + assert(FD->isExplicitlyDefaulted() && CSM != CXXInvalid && "not an explicitly-defaulted special member"); // Whether this was the first-declared instance of the constructor. // This affects whether we implicitly add an exception spec and constexpr. - bool First = MD == MD->getCanonicalDecl(); + FunctionDecl *CanonFD = FD->getCanonicalDecl(); + bool First = FD == CanonFD; + + bool IsMethod = isa(FD); + + // Dig up the record decl in which this function was declared. If it's a + // defaulted non-method comparison operator, the canonical decls lexical + // context will provide this. + + CXXRecordDecl *RD; + if (IsMethod) + RD = cast(FD)->getParent(); + else + RD = dyn_cast(FD->getLexicalParent()); + assert(RD); bool HadError = false; + // C++11 [dcl.fct.def.default]p1: // A function that is explicitly defaulted shall // -- be a special member function (checked elsewhere), @@ -6420,20 +6567,25 @@ unsigned ExpectedParams = 1; if (CSM == CXXDefaultConstructor || CSM == CXXDestructor) ExpectedParams = 0; - if (MD->getNumParams() != ExpectedParams) { - // This also checks for default arguments: a copy or move constructor with a - // default argument is classified as a default constructor, and assignment - // operations and destructors can't have default arguments. - Diag(MD->getLocation(), diag::err_defaulted_special_member_params) - << CSM << MD->getSourceRange(); + else if (!IsMethod) { + assert(CSM == CXXComparisonOperator); + ExpectedParams = 2; + } + if (FD->getNumParams() != ExpectedParams) { + // This also checks for default arguments: a copy or move constructor with + // a default argument is classified as a default constructor, and + // assignment operations and destructors can't have default arguments. + Diag(FD->getLocation(), diag::err_defaulted_special_member_params) + << CSM << FD->getSourceRange(); + HadError = true; - } else if (MD->isVariadic()) { - Diag(MD->getLocation(), diag::err_defaulted_special_member_variadic) - << CSM << MD->getSourceRange(); + } else if (FD->isVariadic()) { + Diag(FD->getLocation(), diag::err_defaulted_special_member_variadic) + << CSM << FD->getSourceRange(); HadError = true; } - const FunctionProtoType *Type = MD->getType()->getAs(); + const FunctionProtoType *Type = FD->getType()->getAs(); bool CanHaveConstParam = false; if (CSM == CXXCopyConstructor) @@ -6448,14 +6600,14 @@ QualType ExpectedReturnType = Context.getLValueReferenceType(Context.getTypeDeclType(RD)); if (!Context.hasSameType(ReturnType, ExpectedReturnType)) { - Diag(MD->getLocation(), diag::err_defaulted_special_member_return_type) - << (CSM == CXXMoveAssignment) << ExpectedReturnType; + Diag(FD->getLocation(), diag::err_defaulted_special_member_return_type) + << CSM << ExpectedReturnType; HadError = true; } // A defaulted special member cannot have cv-qualifiers. if (Type->getTypeQuals()) { - Diag(MD->getLocation(), diag::err_defaulted_special_member_quals) + Diag(FD->getLocation(), diag::err_defaulted_special_member_quals) << (CSM == CXXMoveAssignment) << getLangOpts().CPlusPlus14; HadError = true; } @@ -6464,25 +6616,78 @@ // Check for parameter type matching. QualType ArgType = ExpectedParams ? Type->getParamType(0) : QualType(); bool HasConstParam = false; - if (ExpectedParams && ArgType->isReferenceType()) { + // FIXME(EricWF): Integrate this better + DefaultedComparisonInfo CompareInfo; + if (CSM == CXXComparisonOperator) { + CompareInfo = getDefaultedComparisonInfo(*this, FD, RD, FD->getLocation()); + + auto Opc = BinaryOperator::getOverloadedOpcode(FD->getOverloadedOperator()); + bool IsEqualityOrRelational = BinaryOperator::isEqualityOp(Opc) || + BinaryOperator::isRelationalOp(Opc); + ReturnType = FD->getReturnType(); + if (IsEqualityOrRelational) { + QualType ExpectTy = Context.BoolTy; + if (!Context.hasSameType(ReturnType, ExpectTy)) { + Diag(FD->getLocation(), diag::err_defaulted_special_member_return_type) + << CSM << ExpectTy; + HadError = true; + } + } else { + if (auto *AutoTy = FD->getReturnType()->getAs()) { + if (!CompareInfo.CommonCategoryType.isNull()) + ReturnType = CompareInfo.CommonCategoryType; + else + ReturnType = Context.VoidTy; + } + if (!FD->isInvalidDecl() && + Context.hasSameType(ReturnType, Context.VoidTy)) + CompareInfo.ShouldDelete = true; + } + + // FIXME(EricWF): This isn't currently required by the standard, however, + // not requiring it leads to issues. + if (IsMethod && !Type->isConst()) { + Diag(FD->getLocEnd(), + diag::err_defaulted_comparison_operator_member_non_const) + << FD->getSourceRange(); + HadError = true; + } + + // Check the types of the parameters. They must be 'T const&' where T is + // the record type it is being declared within. + if (FD->getNumParams() == ExpectedParams) { + QualType ExpectedParamType = Context.getLValueReferenceType( + Context.getTypeDeclType(RD).withConst()); + for (unsigned I = 0, End = FD->getNumParams(); I < End; ++I) { + QualType ParamTy = FD->getParamDecl(I)->getType(); + if (!ParamTy->isDependentType() && + !Context.hasSameType(ExpectedParamType, ParamTy)) { + Diag(FD->getLocation(), + diag::err_defaulted_comparison_operator_param_type) + << ExpectedParamType << ParamTy + << FD->getParamDecl(I)->getSourceRange(); + HadError = true; + } + } + } + } else if (ExpectedParams && ArgType->isReferenceType()) { // Argument must be reference to possibly-const T. QualType ReferentType = ArgType->getPointeeType(); HasConstParam = ReferentType.isConstQualified(); if (ReferentType.isVolatileQualified()) { - Diag(MD->getLocation(), - diag::err_defaulted_special_member_volatile_param) << CSM; + Diag(FD->getLocation(), diag::err_defaulted_special_member_volatile_param) + << CSM; HadError = true; } - if (HasConstParam && !CanHaveConstParam) { if (CSM == CXXCopyConstructor || CSM == CXXCopyAssignment) { - Diag(MD->getLocation(), + Diag(FD->getLocation(), diag::err_defaulted_special_member_copy_const_param) << (CSM == CXXCopyAssignment); // FIXME: Explain why this special member can't be const. } else { - Diag(MD->getLocation(), + Diag(FD->getLocation(), diag::err_defaulted_special_member_move_const_param) << (CSM == CXXMoveAssignment); } @@ -6492,7 +6697,7 @@ // A copy assignment operator can take its argument by value, but a // defaulted one cannot. assert(CSM == CXXCopyAssignment && "unexpected non-ref argument"); - Diag(MD->getLocation(), diag::err_defaulted_copy_assign_not_ref); + Diag(FD->getLocation(), diag::err_defaulted_copy_assign_not_ref); HadError = true; } @@ -6503,19 +6708,20 @@ // makes such functions always instantiate to constexpr functions. For // functions which cannot be constexpr (for non-constructors in C++11 and for // destructors in C++1y), this is checked elsewhere. - bool Constexpr = defaultedSpecialMemberIsConstexpr(*this, RD, CSM, - HasConstParam); - if ((getLangOpts().CPlusPlus14 ? !isa(MD) - : isa(MD)) && - MD->isConstexpr() && !Constexpr && - MD->getTemplatedKind() == FunctionDecl::TK_NonTemplate) { - Diag(MD->getLocStart(), diag::err_incorrect_defaulted_constexpr) << CSM; + bool Constexpr = + defaultedSpecialMemberIsConstexpr(*this, RD, CSM, HasConstParam); + if ((getLangOpts().CPlusPlus14 ? !isa(FD) + : isa(FD)) && + FD->isConstexpr() && !Constexpr && + FD->getTemplatedKind() == FunctionDecl::TK_NonTemplate) { + Diag(FD->getLocStart(), diag::err_incorrect_defaulted_constexpr) << CSM; // FIXME: Explain why the special member can't be constexpr. HadError = true; } // and may have an explicit exception-specification only if it is compatible // with the exception-specification on the implicit declaration. + // FIXME(EricWF): Check exception specs? if (Type->hasExceptionSpec()) { // Delay the check if this is the first declaration of the special member, // since we may not have parsed some necessary in-class initializers yet. @@ -6523,46 +6729,57 @@ // If the exception specification needs to be instantiated, do so now, // before we clobber it with an EST_Unevaluated specification below. if (Type->getExceptionSpecType() == EST_Uninstantiated) { - InstantiateExceptionSpec(MD->getLocStart(), MD); - Type = MD->getType()->getAs(); + InstantiateExceptionSpec(FD->getLocStart(), FD); + Type = FD->getType()->getAs(); + } + DelayedDefaultedMemberExceptionSpecs.push_back(std::make_pair(FD, Type)); + } else if (CSM != CXXComparisonOperator) { + CheckExplicitlyDefaultedMemberExceptionSpec(FD, Type); + } else { + // This should be an explicitly-defaulted comparison operator which + // has been re-declared as defaulted outside, so it should already be + // ill formed. + assert(CSM == CXXComparisonOperator && FD->isInvalidDecl()); } - DelayedDefaultedMemberExceptionSpecs.push_back(std::make_pair(MD, Type)); - } else - CheckExplicitlyDefaultedMemberExceptionSpec(MD, Type); } // If a function is explicitly defaulted on its first declaration, if (First) { // -- it is implicitly considered to be constexpr if the implicit // definition would be, - MD->setConstexpr(Constexpr); - + FD->setConstexpr(Constexpr); + llvm::SmallVector Args; + Args.append(ExpectedParams, ArgType); // -- it is implicitly considered to have the same exception-specification // as if it had been implicitly declared, FunctionProtoType::ExtProtoInfo EPI = Type->getExtProtoInfo(); + // FIXME(EricWF) EPI.ExceptionSpec.Type = EST_Unevaluated; - EPI.ExceptionSpec.SourceDecl = MD; - MD->setType(Context.getFunctionType(ReturnType, - llvm::makeArrayRef(&ArgType, - ExpectedParams), - EPI)); + EPI.ExceptionSpec.SourceDecl = FD; + FD->setType(Context.getFunctionType(ReturnType, Args, EPI)); } - if (ShouldDeleteSpecialMember(MD, CSM)) { + bool ShouldDelete = + (CSM == CXXComparisonOperator && + Context.hasSameType(Context.VoidTy, FD->getReturnType())); + if (ShouldDelete || ShouldDeleteSpecialMember(FD, CSM)) { if (First) { - SetDeclDeleted(MD, MD->getLocation()); + SetDeclDeleted(FD, FD->getLocation()); } else { + assert(CSM != CXXComparisonOperator); // FIXME(EricWF) + assert(isa(FD)); + CXXMethodDecl *MD = cast(FD); // C++11 [dcl.fct.def.default]p4: // [For a] user-provided explicitly-defaulted function [...] if such a // function is implicitly defined as deleted, the program is ill-formed. - Diag(MD->getLocation(), diag::err_out_of_line_default_deletes) << CSM; + Diag(FD->getLocation(), diag::err_out_of_line_default_deletes) << CSM; ShouldDeleteSpecialMember(MD, CSM, nullptr, /*Diagnose*/ true); HadError = true; } } if (HadError) - MD->setInvalidDecl(); + FD->setInvalidDecl(); } /// Check whether the exception specification provided for an @@ -6570,18 +6787,19 @@ /// that would have been generated for an implicit special member, per /// C++11 [dcl.fct.def.default]p2. void Sema::CheckExplicitlyDefaultedMemberExceptionSpec( - CXXMethodDecl *MD, const FunctionProtoType *SpecifiedType) { + FunctionDecl *FD, const FunctionProtoType *SpecifiedType) { // If the exception specification was explicitly specified but hadn't been // parsed when the method was defaulted, grab it now. if (SpecifiedType->getExceptionSpecType() == EST_Unparsed) SpecifiedType = - MD->getTypeSourceInfo()->getType()->castAs(); + FD->getTypeSourceInfo()->getType()->castAs(); // Compute the implicit exception specification. - CallingConv CC = Context.getDefaultCallingConvention(/*IsVariadic=*/false, - /*IsCXXMethod=*/true); + CallingConv CC = Context.getDefaultCallingConvention( + /*IsVariadic=*/false, + /*IsCXXMethod=*/isa(FD)); FunctionProtoType::ExtProtoInfo EPI(CC); - auto IES = computeImplicitExceptionSpec(*this, MD->getLocation(), MD); + auto IES = computeImplicitExceptionSpec(*this, FD->getLocation(), FD); EPI.ExceptionSpec = IES.getExceptionSpec(); const FunctionProtoType *ImplicitType = cast( Context.getFunctionType(Context.VoidTy, None, EPI)); @@ -6589,9 +6807,9 @@ // Ensure that it matches. CheckEquivalentExceptionSpec( PDiag(diag::err_incorrect_defaulted_exception_spec) - << getSpecialMember(MD), PDiag(), - ImplicitType, SourceLocation(), - SpecifiedType, MD->getLocation()); + << getSpecialMember(FD), + PDiag(), ImplicitType, SourceLocation(), SpecifiedType, + FD->getLocation()); } void Sema::CheckDelayedMemberExceptionSpecs() { @@ -6615,19 +6833,24 @@ namespace { /// CRTP base class for visiting operations performed by a special member /// function (or inherited constructor). -template -struct SpecialMemberVisitor { +template struct SpecialMemberVisitor { Sema &S; - CXXMethodDecl *MD; + FunctionDecl *FD; Sema::CXXSpecialMember CSM; Sema::InheritedConstructorInfo *ICI; + CXXRecordDecl *Record; // Properties of the special member, computed for convenience. - bool IsConstructor = false, IsAssignment = false, ConstArg = false; + bool IsConstructor = false, IsAssignment = false, IsComparison = false, + ConstArg = false; - SpecialMemberVisitor(Sema &S, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM, + SpecialMemberVisitor(Sema &S, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI) - : S(S), MD(MD), CSM(CSM), ICI(ICI) { + : S(S), FD(FD), CSM(CSM), ICI(ICI) { + if (auto *MD = dyn_cast(FD)) + Record = MD->getParent(); + else + Record = cast(FD->getLexicalParent()); switch (CSM) { case Sema::CXXDefaultConstructor: case Sema::CXXCopyConstructor: @@ -6640,13 +6863,16 @@ break; case Sema::CXXDestructor: break; + case Sema::CXXComparisonOperator: + IsComparison = true; + break; case Sema::CXXInvalid: llvm_unreachable("invalid special member kind"); } - if (MD->getNumParams()) { + if (FD->getNumParams()) { if (const ReferenceType *RT = - MD->getParamDecl(0)->getType()->getAs()) + FD->getParamDecl(0)->getType()->getAs()) ConstArg = RT->getPointeeType().isConstQualified(); } } @@ -6661,7 +6887,7 @@ /// Look up the corresponding special member in the given class. Sema::SpecialMemberOverloadResult lookupIn(CXXRecordDecl *Class, unsigned Quals, bool IsMutable) { - return lookupCallFromSpecialMember(S, Class, CSM, Quals, + return lookupCallFromSpecialMember(S, Record, Class, CSM, Quals, ConstArg && !IsMutable); } @@ -6671,8 +6897,9 @@ if (!ICI) return {}; assert(CSM == Sema::CXXDefaultConstructor); - auto *BaseCtor = - cast(MD)->getInheritedConstructor().getConstructor(); + auto *BaseCtor = cast(FD) + ->getInheritedConstructor() + .getConstructor(); if (auto *MD = ICI->findConstructorForBase(Class, BaseCtor).first) return MD; return {}; @@ -6705,22 +6932,20 @@ // Visit the bases and members of the class. bool visit(BasesToVisit Bases) { - CXXRecordDecl *RD = MD->getParent(); - if (Bases == VisitPotentiallyConstructedBases) - Bases = RD->isAbstract() ? VisitNonVirtualBases : VisitAllBases; + Bases = Record->isAbstract() ? VisitNonVirtualBases : VisitAllBases; - for (auto &B : RD->bases()) + for (auto &B : Record->bases()) if ((Bases == VisitDirectBases || !B.isVirtual()) && getDerived().visitBase(&B)) return true; if (Bases == VisitAllBases) - for (auto &B : RD->vbases()) + for (auto &B : Record->vbases()) if (getDerived().visitBase(&B)) return true; - for (auto *F : RD->fields()) + for (auto *F : Record->fields()) if (!F->isInvalidDecl() && !F->isUnnamedBitfield() && getDerived().visitField(F)) return true; @@ -6739,13 +6964,13 @@ bool AllFieldsAreConst; - SpecialMemberDeletionInfo(Sema &S, CXXMethodDecl *MD, + SpecialMemberDeletionInfo(Sema &S, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI, bool Diagnose) - : SpecialMemberVisitor(S, MD, CSM, ICI), Diagnose(Diagnose), - Loc(MD->getLocation()), AllFieldsAreConst(true) {} + : SpecialMemberVisitor(S, FD, CSM, ICI), Diagnose(Diagnose), + Loc(FD->getLocation()), AllFieldsAreConst(true) {} - bool inUnion() const { return MD->getParent()->isUnion(); } + bool inUnion() const { return Record->isUnion(); } Sema::CXXSpecialMember getEffectiveCSM() { return ICI ? Sema::CXXInvalid : CSM; @@ -6764,20 +6989,25 @@ Sema::SpecialMemberOverloadResult SMOR, bool IsDtorCallInCtor); - bool isAccessible(Subobject Subobj, CXXMethodDecl *D); + bool isAccessible(Subobject Subobj, FunctionDecl *D); }; } /// Is the given special member inaccessible when used on the given /// sub-object. bool SpecialMemberDeletionInfo::isAccessible(Subobject Subobj, - CXXMethodDecl *target) { + FunctionDecl *FnTarget) { + auto *target = dyn_cast(FnTarget); + // we're handling a friend declaration for an explicitly-defaulted + // comparison operator + if (!target) + return true; /// If we're operating on a base class, the object type is the /// type of this special member. QualType objectTy; AccessSpecifier access = target->getAccess(); if (CXXBaseSpecifier *base = Subobj.dyn_cast()) { - objectTy = S.Context.getTypeDeclType(MD->getParent()); + objectTy = S.Context.getTypeDeclType(Record); access = CXXRecordDecl::MergeAccess(base->getAccessSpecifier(), access); // If we're operating on a field, the object type is the type of the field. @@ -6788,24 +7018,52 @@ return S.isSpecialMemberAccessibleForDeletion(target, access, objectTy); } +static bool shouldDeleteForBuiltin(Sema &S, QualType ArgType, + SourceLocation Loc) { + using CCT = ComparisonCategoryType; + ComparisonCategoryType ReturnKind; + if (ArgType->hasFloatingRepresentation()) { + ReturnKind = CCT::PartialOrdering; + } else if (ArgType->isIntegralOrEnumerationType()) { + ReturnKind = CCT::StrongOrdering; + } else if (ArgType->isMemberPointerType()) { + ReturnKind = CCT::StrongEquality; + } else if (ArgType->isPointerType()) { + ReturnKind = CCT::StrongOrdering; + } else { + // Not a type supported for builtin comparisons. (likely an array type) + return true; + } + // Require that the header has already been included and that + // computed comparison category declaration has been found. + return S.CheckComparisonCategoryType(ReturnKind, Loc).isNull(); +} + /// Check whether we should delete a special member due to the implicit /// definition containing a call to a special member of a subobject. bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( Subobject Subobj, Sema::SpecialMemberOverloadResult SMOR, bool IsDtorCallInCtor) { - CXXMethodDecl *Decl = SMOR.getMethod(); + // FIXME(EricWF) This is wrong, it needs to handle functions soon + FunctionDecl *Decl = SMOR.getFunction(); FieldDecl *Field = Subobj.dyn_cast(); - + bool IsTrivial = false; + if (auto *MD = dyn_cast_or_null(Decl)) + IsTrivial = MD->isTrivial(); int DiagKind = -1; - - if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) + if (SMOR.hasBuiltin() && + shouldDeleteForBuiltin(S, SMOR.getBuiltinArgType(), FD->getLocation())) { + assert(CSM == Sema::CXXComparisonOperator); + DiagKind = 0; + } else if (SMOR.getKind() == + Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) DiagKind = !Decl ? 0 : 1; else if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Ambiguous) DiagKind = 2; else if (!isAccessible(Subobj, Decl)) DiagKind = 3; else if (!IsDtorCallInCtor && Field && Field->getParent()->isUnion() && - !Decl->isTrivial()) { + !IsTrivial && CSM != Sema::CXXComparisonOperator) { // A member of a union must have a trivial corresponding special member. // As a weird special case, a destructor call from a union's constructor // must be accessible and non-deleted, but need not be trivial. Such a @@ -6821,14 +7079,14 @@ if (Field) { S.Diag(Field->getLocation(), diag::note_deleted_special_member_class_subobject) - << getEffectiveCSM() << MD->getParent() << /*IsField*/true - << Field << DiagKind << IsDtorCallInCtor; + << getEffectiveCSM() << Record << /*IsField*/ true << Field + << DiagKind << IsDtorCallInCtor; } else { CXXBaseSpecifier *Base = Subobj.get(); S.Diag(Base->getLocStart(), diag::note_deleted_special_member_class_subobject) - << getEffectiveCSM() << MD->getParent() << /*IsField*/false - << Base->getType() << DiagKind << IsDtorCallInCtor; + << getEffectiveCSM() << Record << /*IsField*/ false << Base->getType() + << DiagKind << IsDtorCallInCtor; } if (DiagKind == 1) @@ -6860,8 +7118,8 @@ // C++11 [class.dtor]p5: // -- any direct or virtual base class [...] has a type with a destructor // that is deleted or inaccessible - if (!(CSM == Sema::CXXDefaultConstructor && - Field && Field->hasInClassInitializer()) && + if (!(CSM == Sema::CXXDefaultConstructor && Field && + Field->hasInClassInitializer()) && shouldDeleteForSubobjectCall(Subobj, lookupIn(Class, Quals, IsMutable), false)) return true; @@ -6870,9 +7128,8 @@ // -- any direct or virtual base class or non-static data member has a // type with a destructor that is deleted or inaccessible if (IsConstructor) { - Sema::SpecialMemberOverloadResult SMOR = - S.LookupSpecialMember(Class, Sema::CXXDestructor, - false, false, false, false, false); + Sema::SpecialMemberOverloadResult SMOR = S.LookupSpecialMember( + Class, Sema::CXXDestructor, false, false, false, false, false); if (shouldDeleteForSubobjectCall(Subobj, SMOR, true)) return true; } @@ -6891,7 +7148,7 @@ // If we have an inheriting constructor, check whether we're calling an // inherited constructor instead of a default constructor. Sema::SpecialMemberOverloadResult SMOR = lookupInheritedCtor(BaseClass); - if (auto *BaseCtor = SMOR.getMethod()) { + if (auto *BaseCtor = SMOR.getFunction()) { // Note that we do not check access along this path; other than that, // this is the same as shouldDeleteForSubobjectCall(Base, BaseCtor, false); // FIXME: Check that the base has a usable destructor! Sink this into @@ -6899,8 +7156,8 @@ if (BaseCtor->isDeleted() && Diagnose) { S.Diag(Base->getLocStart(), diag::note_deleted_special_member_class_subobject) - << getEffectiveCSM() << MD->getParent() << /*IsField*/false - << Base->getType() << /*Deleted*/1 << /*IsDtorCallInCtor*/false; + << getEffectiveCSM() << Record << /*IsField*/ false << Base->getType() + << /*Deleted*/ 1 << /*IsDtorCallInCtor*/ false; S.NoteDeletedFunction(BaseCtor); } return BaseCtor->isDeleted(); @@ -6920,7 +7177,7 @@ if (FieldType->isReferenceType() && !FD->hasInClassInitializer()) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_default_ctor_uninit_field) - << !!ICI << MD->getParent() << FD << FieldType << /*Reference*/0; + << !!ICI << Record << FD << FieldType << /*Reference*/ 0; return true; } // C++11 [class.ctor]p5: any non-variant non-static data member of @@ -6932,7 +7189,7 @@ (!FieldRecord || !FieldRecord->hasUserProvidedDefaultConstructor())) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_default_ctor_uninit_field) - << !!ICI << MD->getParent() << FD << FD->getType() << /*Const*/1; + << !!ICI << Record << FD << FD->getType() << /*Const*/ 1; return true; } @@ -6944,7 +7201,7 @@ if (FieldType->isRValueReferenceType()) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_copy_ctor_rvalue_reference) - << MD->getParent() << FD << FieldType; + << Record << FD << FieldType; return true; } } else if (IsAssignment) { @@ -6952,7 +7209,7 @@ if (FieldType->isReferenceType()) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_assign_field) - << isMove() << MD->getParent() << FD << FieldType << /*Reference*/0; + << isMove() << Record << FD << FieldType << /*Reference*/ 0; return true; } if (!FieldRecord && FieldType.isConstQualified()) { @@ -6960,10 +7217,12 @@ // -- a non-static data member of const non-class type (or array thereof) if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_assign_field) - << isMove() << MD->getParent() << FD << FD->getType() << /*Const*/1; + << isMove() << Record << FD << FD->getType() << /*Const*/ 1; return true; } + } else if (IsComparison) { } + // FIXME(EricWF) if (FieldRecord) { // Some additional restrictions exist on the variant members. @@ -6991,7 +7250,7 @@ if (Diagnose) S.Diag(FieldRecord->getLocation(), diag::note_deleted_default_ctor_all_const) - << !!ICI << MD->getParent() << /*anonymous union*/1; + << !!ICI << Record << /*anonymous union*/ 1; return true; } @@ -7016,15 +7275,14 @@ // default constructor. Don't do that. if (CSM == Sema::CXXDefaultConstructor && inUnion() && AllFieldsAreConst) { bool AnyFields = false; - for (auto *F : MD->getParent()->fields()) + for (auto *F : Record->fields()) if ((AnyFields = !F->isUnnamedBitfield())) break; if (!AnyFields) return false; if (Diagnose) - S.Diag(MD->getParent()->getLocation(), - diag::note_deleted_default_ctor_all_const) - << !!ICI << MD->getParent() << /*not anonymous union*/0; + S.Diag(Record->getLocation(), diag::note_deleted_default_ctor_all_const) + << !!ICI << Record << /*not anonymous union*/ 0; return true; } return false; @@ -7033,12 +7291,17 @@ /// Determine whether a defaulted special member function should be defined as /// deleted, as specified in C++11 [class.ctor]p5, C++11 [class.copy]p11, /// C++11 [class.copy]p23, and C++11 [class.dtor]p5. -bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, CXXSpecialMember CSM, +bool Sema::ShouldDeleteSpecialMember(FunctionDecl *FD, CXXSpecialMember CSM, InheritedConstructorInfo *ICI, bool Diagnose) { - if (MD->isInvalidDecl()) + if (FD->isInvalidDecl()) return false; - CXXRecordDecl *RD = MD->getParent(); + CXXRecordDecl *RD; + if (auto *MD = dyn_cast(FD)) + RD = MD->getParent(); + else + RD = cast(FD->getLexicalParent()); + assert(!RD->isDependentType() && "do deletion after instantiation"); if (!LangOpts.CPlusPlus11 || RD->isInvalidDecl()) return false; @@ -7065,7 +7328,7 @@ // If the class definition declares a move constructor or move assignment // operator, an implicitly declared copy constructor or copy assignment // operator is defined as deleted. - if (MD->isImplicit() && + if (FD->isImplicit() && (CSM == CXXCopyConstructor || CSM == CXXCopyAssignment)) { CXXMethodDecl *UserDeclaredMove = nullptr; @@ -7112,24 +7375,24 @@ } // Do access control from the special member function - ContextRAII MethodContext(*this, MD); + ContextRAII MethodContext(*this, FD); // C++11 [class.dtor]p5: // -- for a virtual destructor, lookup of the non-array deallocation function // results in an ambiguity or in a function that is deleted or inaccessible - if (CSM == CXXDestructor && MD->isVirtual()) { + if (CSM == CXXDestructor && cast(FD)->isVirtual()) { FunctionDecl *OperatorDelete = nullptr; DeclarationName Name = Context.DeclarationNames.getCXXOperatorName(OO_Delete); - if (FindDeallocationFunction(MD->getLocation(), MD->getParent(), Name, - OperatorDelete, /*Diagnose*/false)) { + if (FindDeallocationFunction(FD->getLocation(), RD, Name, OperatorDelete, + /*Diagnose*/ false)) { if (Diagnose) Diag(RD->getLocation(), diag::note_deleted_dtor_no_operator_delete); return true; } } - SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose); + SpecialMemberDeletionInfo SMI(*this, FD, CSM, ICI, Diagnose); // Per DR1611, do not consider virtual bases of constructors of abstract // classes, since we are not going to construct them. @@ -7137,14 +7400,18 @@ // classes either. // Per DR2180, for assignment operators we only assign (and thus only // consider) direct bases. - if (SMI.visit(SMI.IsAssignment ? SMI.VisitDirectBases + // FIXME(EricWF): IS this correct? + if (SMI.visit((SMI.IsAssignment || SMI.IsComparison) + ? SMI.VisitDirectBases : SMI.VisitPotentiallyConstructedBases)) return true; if (SMI.shouldDeleteForAllConstMembers()) return true; - if (getLangOpts().CUDA) { + // FIXME(EricWF) + auto *MD = dyn_cast(FD); + if (getLangOpts().CUDA && MD) { // We should delete the special member in CUDA mode if target inference // failed. return inferCUDATargetForImplicitSpecialMember(RD, CSM, MD, SMI.ConstArg, @@ -7176,7 +7443,8 @@ switch (CSM) { case Sema::CXXInvalid: llvm_unreachable("not a special member"); - + case Sema::CXXComparisonOperator: + llvm_unreachable("handled elsewhere"); case Sema::CXXDefaultConstructor: // C++11 [class.ctor]p5: // A default constructor is trivial if: @@ -7262,8 +7530,8 @@ case Sema::CXXMoveConstructor: case Sema::CXXMoveAssignment: NeedOverloadResolution: - Sema::SpecialMemberOverloadResult SMOR = - lookupCallFromSpecialMember(S, RD, CSM, Quals, ConstRHS); + Sema::SpecialMemberOverloadResult SMOR = lookupCallFromSpecialMember( + S, /*EvaluatingClass*/ nullptr, RD, CSM, Quals, ConstRHS); // The standard doesn't describe how to behave if the lookup is ambiguous. // We treat it as not making the member non-trivial, just like the standard @@ -7272,7 +7540,7 @@ if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Ambiguous) return true; - if (!SMOR.getMethod()) { + if (!SMOR.getFunction()) { assert(SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted); return false; @@ -7280,13 +7548,14 @@ // We deliberately don't check if we found a deleted special member. We're // not supposed to! + CXXMethodDecl *MD = cast(SMOR.getFunction()); if (Selected) - *Selected = SMOR.getMethod(); + *Selected = MD; if (TAH == Sema::TAH_ConsiderTrivialABI && (CSM == Sema::CXXCopyConstructor || CSM == Sema::CXXMoveConstructor)) - return SMOR.getMethod()->isTrivialForCall(); - return SMOR.getMethod()->isTrivial(); + return MD->isTrivialForCall(); + return MD->isTrivial(); } llvm_unreachable("unknown special method kind"); @@ -7434,9 +7703,15 @@ /// Determine whether a defaulted or deleted special member function is trivial, /// as specified in C++11 [class.ctor]p5, C++11 [class.copy]p12, /// C++11 [class.copy]p25, and C++11 [class.dtor]p5. -bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMember CSM, +bool Sema::SpecialMemberIsTrivial(FunctionDecl *FD, CXXSpecialMember CSM, TrivialABIHandling TAH, bool Diagnose) { - assert(!MD->isUserProvided() && CSM != CXXInvalid && "not special enough"); + assert(CSM != CXXInvalid && "not special enough"); + + if (CSM == CXXComparisonOperator) + return false; + + auto *MD = cast(FD); + assert(MD && !MD->isUserProvided() && "not special enough"); CXXRecordDecl *RD = MD->getParent(); @@ -7483,7 +7758,8 @@ } break; } - + case Sema::CXXComparisonOperator: + llvm_unreachable("handled elsewhere"); case CXXInvalid: llvm_unreachable("not a special member"); } @@ -8894,6 +9170,87 @@ return StdExperimentalNamespaceCache; } +QualType Sema::CheckComparisonCategoryType(ComparisonCategoryType Kind, + SourceLocation Loc) { + assert(getLangOpts().CPlusPlus && + "Looking for comparison category type outside of C++."); + + // Check if we've already successfully checked the comparison category type + // before. If so, skip checking it again. + ComparisonCategoryInfo *Info = Context.CompCategories.lookupInfo(Kind); + if (Info && FullyCheckedComparisonCategories.count(Info)) + return QualType(Info->CCDecl->getTypeForDecl(), 0); + + // If lookup failed + if (!Info) { + Diag(Loc, diag::err_implied_comparison_category_type_not_found) + << ComparisonCategories::getCategoryString(Kind); + return QualType(); + } + + QualType CCTy(Info->CCDecl->getTypeForDecl(), 0); + // Use an elaborated type for diagnostics which has a name containing the + // prepended 'std' namespace but not any inline namespace names. + QualType TyForDiags = [&]() { + auto *NNS = + NestedNameSpecifier::Create(Context, nullptr, getStdNamespace()); + return Context.getElaboratedType(ETK_None, NNS, CCTy); + }(); + if (RequireCompleteType(Loc, TyForDiags, diag::err_incomplete_type)) + return QualType(); + + assert(Info->Kind == Kind); + assert(Info->CCDecl); + + // Build each of the require values and store them in Info. + for (ComparisonCategoryResult CCR : + ComparisonCategories::getPossibleResultsForType(Kind)) { + + VarDecl *VD = Info->lookupResultDecl(CCR); + if (!VD) { + Diag(Loc, diag::err_std_compare_type_missing_member) + << TyForDiags << ComparisonCategories::getResultString(CCR); + return QualType(); + } + // Attempt to diagnose reasons why the STL definition of this type + // might be foobar, including it failing to be a constant expression. + // TODO Handle more ways the lookup or result can be invalid. + if (!VD->isStaticDataMember() || !VD->isConstexpr()) { + Diag(Loc, diag::err_std_compare_type_not_supported) + << TyForDiags << ComparisonCategories::getResultString(CCR); + Diag(VD->getLocation(), diag::note_var_declared_here) + << VD << VD->getSourceRange(); + return QualType(); + } + MarkVariableReferenced(Loc, VD); + } + + // We've successfully built the required types and expressions. Update + // the cache and return the newly cached value. + FullyCheckedComparisonCategories.insert(Info); + return QualType(Info->CCDecl->getTypeForDecl(), 0); +} + +QualType Sema::ComputeCommonComparisonType(ArrayRef Types, + SourceLocation Loc) { + using CCT = ComparisonCategoryType; + // Check that has been included. We also might need to reference + // 'std::strong_ordering::equal' in a explicitly-defaulted comparison + // operator. + QualType SOType = CheckComparisonCategoryType(CCT::StrongOrdering, Loc); + if (SOType.isNull()) + return QualType(); + // OK, we *should* have all the comparison category types available. Try to + // compute the common type. Failure to do so should indicate one of the + // specified types is not a CCT. + const ComparisonCategoryInfo *Info = + Context.CompCategories.computeCommonComparisonType(Types); + if (!Info) + return Context.VoidTy; + // Success! + return QualType(Info->CCDecl->getTypeForDecl(), 0); +} + /// \brief Retrieve the special "std" namespace, which may require us to /// implicitly define the namespace. NamespaceDecl *Sema::getOrCreateStdNamespace() { @@ -10457,11 +10814,11 @@ SourceLocation Loc; Sema::ImplicitExceptionSpecification ExceptSpec; - SpecialMemberExceptionSpecInfo(Sema &S, CXXMethodDecl *MD, + SpecialMemberExceptionSpecInfo(Sema &S, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI, SourceLocation Loc) - : SpecialMemberVisitor(S, MD, CSM, ICI), Loc(Loc), ExceptSpec(S) {} + : SpecialMemberVisitor(S, FD, CSM, ICI), Loc(Loc), ExceptSpec(S) {} bool visitBase(CXXBaseSpecifier *Base); bool visitField(FieldDecl *FD); @@ -10481,7 +10838,7 @@ auto *BaseClass = cast(RT->getDecl()); Sema::SpecialMemberOverloadResult SMOR = lookupInheritedCtor(BaseClass); - if (auto *BaseCtor = SMOR.getMethod()) { + if (auto *BaseCtor = cast_or_null(SMOR.getFunction())) { visitSubobjectCall(Base, BaseCtor); return false; } @@ -10522,21 +10879,20 @@ Subobject Subobj, Sema::SpecialMemberOverloadResult SMOR) { // Note, if lookup fails, it doesn't matter what exception specification we // choose because the special member will be deleted. - if (CXXMethodDecl *MD = SMOR.getMethod()) - ExceptSpec.CalledDecl(getSubobjectLoc(Subobj), MD); + if (FunctionDecl *FD = SMOR.getFunction()) + ExceptSpec.CalledDecl(getSubobjectLoc(Subobj), FD); } static Sema::ImplicitExceptionSpecification ComputeDefaultedSpecialMemberExceptionSpec( - Sema &S, SourceLocation Loc, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM, + Sema &S, SourceLocation Loc, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI) { - CXXRecordDecl *ClassDecl = MD->getParent(); // C++ [except.spec]p14: // An implicitly declared special member function (Clause 12) shall have an // exception-specification. [...] - SpecialMemberExceptionSpecInfo Info(S, MD, CSM, ICI, Loc); - if (ClassDecl->isInvalidDecl()) + SpecialMemberExceptionSpecInfo Info(S, FD, CSM, ICI, Loc); + if (Info.Record->isInvalidDecl()) return Info.ExceptSpec; // C++1z [except.spec]p7: @@ -11106,6 +11462,29 @@ } }; +class RefOrThisBuilder : public ExprBuilder { + VarDecl *Var = nullptr; + QualType VarType; + +public: + Expr *build(Sema &S, SourceLocation Loc) const override { + if (Var) + return assertNotNull( + S.BuildDeclRefExpr(Var, VarType, VK_LValue, Loc).get()); + else { + Expr *E = assertNotNull(S.ActOnCXXThis(Loc).getAs()); + return assertNotNull(S.CreateBuiltinUnaryOp(Loc, UO_Deref, E).get()); + } + } + + void makeRefBuilder(VarDecl *xVar, QualType VarTy) { + Var = xVar; + VarType = VarTy; + } + + bool isThisBuilder() const { return Var == nullptr; } +}; + class CastBuilder: public ExprBuilder { const ExprBuilder &Builder; QualType Type; @@ -11931,8 +12310,8 @@ /*ConstArg*/false, /*VolatileArg*/false, /*RValueThis*/true, /*ConstThis*/false, /*VolatileThis*/false); - if (!SMOR.getMethod() || SMOR.getMethod()->isTrivial() || - !SMOR.getMethod()->isMoveAssignmentOperator()) + CXXMethodDecl *MD = cast_or_null(SMOR.getFunction()); + if (!MD || MD->isTrivial() || !MD->isMoveAssignmentOperator()) continue; if (BaseSpec->isVirtual()) { @@ -11963,7 +12342,7 @@ // Only walk over bases that have defaulted move assignment operators. // We assume that any user-provided move assignment operator handles // the multiple-moves-of-vbase case itself somehow. - if (!SMOR.getMethod()->isDefaulted()) + if (!SMOR.getFunction()->isDefaulted()) continue; // We're going to move the base classes of Base. Add them to the list. @@ -12560,6 +12939,330 @@ } } +namespace { +class ComparisonBuilder { + Sema &S; + FunctionDecl *CompareOperator; + SmallVector Statements; + BinaryOperatorKind Opc; + + Expr *LastCmpResult = nullptr; + +public: + SourceLocation Loc; + + ComparisonBuilder(Sema &S, FunctionDecl *CompareOperator) + : S(S), CompareOperator(CompareOperator) { + // Our location for everything implicitly-generated. + Loc = CompareOperator->getLocEnd().isValid() + ? CompareOperator->getLocEnd() + : CompareOperator->getLocation(); + Opc = BinaryOperator::getOverloadedOpcode( + CompareOperator->getOverloadedOperator()); + } + + bool BuildCompare(const ExprBuilder &LHSB, const ExprBuilder &RHSB) { + // Build a call to the comparison function for the specified LHS and RHS + // expressions. + Expr *LHS = LHSB.build(S, Loc); + Expr *RHS = RHSB.build(S, Loc); + ExprResult Cmp = S.BuildBinOp(nullptr, Loc, BO_Cmp, LHS, RHS); + if (Cmp.isInvalid()) + return Error(); + + // Stash the result of that comparison in case it's the final comparison, + // and get the previously stashed comparison if it exists. + Expr *LastCmp = Cmp.get(); + std::swap(LastCmp, LastCmpResult); + + if (!LastCmp) + return false; + + // If there was a previously stashed comparison, build an expression of the + // form: + // if (auto result = ; result != 0) + // return result; + StmtResult BranchOnCmp = BuildCompareEqualOrReturn(LastCmp); + if (BranchOnCmp.isInvalid()) + return Error(); + + // Success! + Statements.push_back(BranchOnCmp.getAs()); + return false; + } + + bool Finalize() { + // If we don't have a previously stashed comparison (ie the class for this + // comparison operator is empty), build a result representing equality + // (or inequality when the comparison operator is !=, <, or >) + if (!LastCmpResult) { + ExprResult EqualExpr = BuildReturnValueForEquality(); + if (EqualExpr.isInvalid()) + return Error(); + LastCmpResult = EqualExpr.get(); + } + + // Add a final return statement which returns the result of the last + // comparison or the newly generated result if there was none. + StmtResult Return = S.BuildReturnStmt(Loc, LastCmpResult); + if (Return.isInvalid()) + return Error(); + Statements.push_back(Return.getAs()); + + StmtResult Body; + { + Sema::CompoundScopeRAII CompoundScope(S); + Body = S.ActOnCompoundStmt(Loc, Loc, Statements, + /*isStmtExpr=*/false); + assert(!Body.isInvalid() && "Compound statement creation cannot fail"); + } + CompareOperator->setBody(Body.getAs()); + CompareOperator->markUsed(S.Context); + return false; + } + +private: + bool Error() { + CompareOperator->setInvalidDecl(); + return true; + } + + /// FIXME(EricWF): Document this + StmtResult BuildCompareEqualOrReturn(Expr *LastResult) { + assert(LastResult && "should not be null"); + + VarDecl *VD = BuildInventedVarDecl(LastResult); + if (!VD) + return StmtError(); + Expr *DRE = BuildDeclRef(VD); + + // Now attempt to build the full expression '(LHS <=> RHS) @ 0' using the + // evaluated operand and the literal 0, where '@' is != for three-way + // comparisons, and the negated comparison operator otherwise. + BinaryOperatorKind NegatedOp = + Opc == BO_Cmp ? BO_NE : BinaryOperator::negateComparisonOp(Opc); + + ExprResult CmpRes = + S.BuildBinOp(nullptr, Loc, NegatedOp, DRE, BuildZeroLiteral()); + if (CmpRes.isInvalid()) + return StmtError(); + + StmtResult VDDeclStmt = + S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(VD), Loc, Loc); + if (VDDeclStmt.isInvalid()) + return false; + + Expr *ReturnExpr = DRE; + if (Opc != BO_Cmp) + ReturnExpr = + new (S.Context) CXXBoolLiteralExpr(false, S.Context.BoolTy, Loc); + + // If the last comparison returned not equal, we'll return it's value. + StmtResult Return = S.BuildReturnStmt(Loc, ReturnExpr); + if (Return.isInvalid()) + return StmtError(); + + if (cast(Return.get())->getNRVOCandidate() == VD) + VD->setNRVOVariable(true); + + return S.ActOnIfStmt(Loc, false, VDDeclStmt.getAs(), + S.ActOnCondition(nullptr, Loc, CmpRes.get(), + Sema::ConditionKind::Boolean), + Return.get(), SourceLocation(), nullptr); + } + + /// Build an expression which will be returned for comparisons which + /// result in equality. For three-way comparisons this is + /// 'std::strong_ordering::equal', otherwise it's a boolean with the value + /// 'true' if the specified opcode checks for equality (including <= or >=) + /// and false otherwise. + /// + /// Note: This should only be used when the struct being compared is empty. + /// Otherwise the result of the previous comparison should be returned. + ExprResult BuildReturnValueForEquality() { + if (Opc != BO_Cmp) { + bool Val = Opc != BO_NE && Opc != BO_LT && Opc != BO_GT; + return new (S.Context) CXXBoolLiteralExpr(Val, S.Context.BoolTy, Loc); + } + + // FIXME: we need to emit diagnostics here + QualType RetTy = S.CheckComparisonCategoryType( + ComparisonCategoryType::StrongOrdering, Loc); + if (RetTy.isNull()) + return ExprError(); + + const VarDecl *VDC = + S.Context.CompCategories.getInfoForType(RetTy).lookupResultDecl( + ComparisonCategoryResult::Equal); + VarDecl *VD = const_cast(VDC); + return S.BuildDeclRefExpr(VD, RetTy, VK_LValue, Loc); + } + + /// Build a variable declaration to store the result of a comparison + /// expression. This declaration will be inserted into the initializer clause + /// of an IfStmt so it can be used as both the condition and return statement + /// inside the IfStmt body. + VarDecl *BuildInventedVarDecl(Expr *Init) { + // Build a dummy variable to hold the result of our last comparison. + auto *VD = VarDecl::Create( + S.Context, CompareOperator, Loc, Loc, + &S.PP.getIdentifierTable().get("__cmp_res"), Init->getType(), + S.Context.getTrivialTypeSourceInfo(Init->getType(), Loc), SC_Auto); + S.CheckVariableDeclarationType(VD); + if (VD->isInvalidDecl()) + return nullptr; + + // Initialize the dummy variable using the last comparison expression as + // the initializer. + InitializedEntity Entity = InitializedEntity::InitializeVariable(VD); + InitializationKind Kind = InitializationKind::CreateForInit( + VD->getLocation(), /*DirectInit=*/true, Init); + InitializationSequence InitSeq(S, Entity, Kind, Init, + /*TopLevelOfInitList=*/false, + /*TreatUnavailableAsInvalid=*/false); + if (!InitSeq) + return nullptr; + ExprResult InitResult = InitSeq.Perform(S, Entity, Kind, Init); + if (InitResult.isInvalid()) + return nullptr; + InitResult = S.ActOnFinishFullExpr(InitResult.get()); + if (InitResult.isInvalid()) + return nullptr; + + S.AddInitializerToDecl(VD, InitResult.get(), /*DirectInit=*/false); + + S.CheckCompleteVariableDeclaration(VD); + return VD; + } + + Expr *BuildDeclRef(VarDecl *VD) { + ExprResult DRE = + S.BuildDeclRefExpr(VD, VD->getType(), ExprValueKind::VK_LValue, Loc); + assert(!DRE.isInvalid()); + return DRE.get(); + } + + Expr *BuildZeroLiteral() { + llvm::APInt I = + llvm::APInt::getNullValue(S.Context.getIntWidth(S.Context.IntTy)); + return IntegerLiteral::Create(S.Context, I, S.Context.IntTy, + SourceLocation()); + } +}; +} + +void Sema::DefineDefaultedComparisonOperator(SourceLocation CurrentLocation, + FunctionDecl *CompareOperator) { + assert(CompareOperator->isDefaultComparisonOperator() && + !CompareOperator->isDeleted() && + !CompareOperator->doesThisDeclarationHaveABody()); + assert(CompareOperator->isInlined() && + "defaulted comparison operator should be marked as implicitly " + "inline"); + if (CompareOperator->isInvalidDecl()) + return; + + const bool IsMethod = isa(CompareOperator); + + CXXRecordDecl *ClassDecl = + cast(CompareOperator->getLexicalParent()); + SynthesizedFunctionScope Scope(*this, CompareOperator); + // FIXME(EricWF): What does the spec say about implicit exception specs for + // defaulted comparison operators? + + // Add a context note for diagnostics produced after this point. + Scope.addContextNote(CurrentLocation); + + auto CheckParam = [&](ParmVarDecl *PD) { + QualType RefTy = + PD->getType()->getAs()->getPointeeType(); + auto Quals = RefTy.getQualifiers(); + Quals.removeConst(); + assert(!Quals && "Bad argument type for defaulted comparison"); + return RefTy; + }; + + RefOrThisBuilder LHSRef; + if (!IsMethod) { + ParmVarDecl *VD = CompareOperator->getParamDecl(0); + LHSRef.makeRefBuilder(VD, CheckParam(VD)); + } + + // The parameter for the "other" object. + ParmVarDecl *RHSPD = CompareOperator->getParamDecl(IsMethod ? 0 : 1); + QualType RHSRefType = CheckParam(RHSPD); + // Builds a reference to the "other" object. + RefBuilder RHSRef(RHSPD, RHSRefType); + + auto CallQuals = + CompareOperator->getType()->getAs()->getTypeQuals(); + + ComparisonBuilder Builder(*this, CompareOperator); + + // compare base classes + bool Invalid = false; + for (auto &Base : ClassDecl->bases()) { + // C++2a [class.spaceship]p1: + // It is unspecified whether virtual base class subobjects are compared + // more than once. + + QualType BaseType = Base.getType(); + if (!BaseType->isRecordType()) { + Invalid = true; + continue; + } + + CXXCastPath BasePath; + BasePath.push_back(&Base); + + // Implicitly cast "this" to the appropriately-qualified base type. + CastBuilder LHS(LHSRef, Context.getCVRQualifiedType(BaseType, CallQuals), + VK_LValue, BasePath); + + // Construct the "from" expression, which is an implicit cast to the + // appropriately-qualified base type. + CastBuilder RHS(RHSRef, BaseType, VK_LValue, BasePath); + + if (Builder.BuildCompare(LHS, RHS)) + return; + } + + // Assign non-static members. + for (auto *Field : ClassDecl->fields()) { + if (Field->isInvalidDecl()) { + Invalid = true; + continue; + } + + // Suppress assigning zero-width bitfields. + if (Field->isZeroLengthBitField(Context)) + continue; + + // Build references to the field in the object we're copying from and to. + LookupResult MemberLookup(*this, Field->getDeclName(), Builder.Loc, + LookupMemberName); + MemberLookup.addDecl(Field); + MemberLookup.resolveKind(); + MemberBuilder RHS(RHSRef, RHSRefType, /*IsArrow=*/false, MemberLookup); + MemberBuilder LHS(LHSRef, RHSRefType, /*IsArrow=*/false, MemberLookup); + + if (Builder.BuildCompare(LHS, RHS)) + return; + } + + if (Invalid) { + CompareOperator->setInvalidDecl(); + return; + } + + if (Builder.Finalize()) + return; + + if (ASTMutationListener *L = getASTMutationListener()) { + L->CompletedImplicitDefinition(CompareOperator); + } +} + /// \brief Determine whether the given list arguments contains exactly one /// "real" (non-default) argument. static bool hasOneRealArgument(MultiExprArg Args) { @@ -14208,7 +14911,7 @@ FD = FTD->getTemplatedDecl(); else FD = cast(ND); - + assert(!FD->isExplicitlyDefaulted()); // C++11 [dcl.fct.default]p4: If a friend declaration specifies a // default argument expression, that declaration shall be a definition // and shall be the only declaration of the function or function @@ -14305,17 +15008,77 @@ Fn->setDeletedAsWritten(); } +bool Sema::checkDefaultedComparisonOperatorDecl(SourceLocation DefaultLoc, + Decl *Dcl) { + if (!Dcl) + return false; + + // If we don't have a defaulted comparison decl, return. + auto *FD = Dcl->getAsFunction(); + if (!FD || !FD->isDefaultComparisonOperator(true)) + return false; + + // Require defaulted comparison decls to be the first declaration for a + // given function. + Decl *CanonDcl = Dcl->getCanonicalDecl(); + if (CanonDcl != Dcl) { + // FIXME(EricWF): IDK if this is strictly required by the standard, + // but I don't want to handle it now. + // TODO: Handle this better? + Diag(FD->getLocation(), diag::err_defaulted_decl_not_first); + Diag(CanonDcl->getLocation(), diag::note_previous_declaration) + << CanonDcl->getSourceRange(); + Dcl->setInvalidDecl(); + return true; + } + + // Dig up the record decl in which this function was declared. If it's a + // defaulted non-method comparison operator, the canonical decls lexical + // context will provide this. + CXXRecordDecl *RD; + if (auto *MD = dyn_cast(FD)) + RD = MD->getParent(); + else + RD = dyn_cast(FD->getLexicalParent()); + if (!RD) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_operator_decl_scope); + Dcl->setInvalidDecl(); + return true; + } + + // defaulted comparison operators are not allowed to be template functions. + if (isa(Dcl)) { + Diag(FD->getLocStart(), diag::err_defaulted_comparison_operator_template); + Dcl->setInvalidDecl(); + return true; + } + + // non-valid binary operators get diagnosed elsewhere. For now just ensure + // we skip further checking. + unsigned ExpectedParams = isa(FD) ? 1 : 2; + if (ExpectedParams != FD->getNumParams()) + return true; + + FD->setImplicitlyInline(); + return false; +} + void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) { - CXXMethodDecl *MD = dyn_cast_or_null(Dcl); + // If this is the first time we've seen a friend decl for a defaulted + // comparison operator, we need to do some initial checking on the + // declaration. + if (checkDefaultedComparisonOperatorDecl(DefaultLoc, Dcl)) + return; - if (MD) { + if (auto *MD = dyn_cast_or_null(Dcl)) { if (MD->getParent()->isDependentType()) { MD->setDefaulted(); MD->setExplicitlyDefaulted(); return; } - CXXSpecialMember Member = getSpecialMember(MD); + CXXSpecialMember Member = + getSpecialMember(MD, /*IsExplicitlyDefaulted*/ true); if (Member == CXXInvalid) { if (!MD->isInvalidDecl()) Diag(DefaultLoc, diag::err_default_special_members); @@ -14328,7 +15091,7 @@ // Unset that we will have a body for this function. We might not, // if it turns out to be trivial, and we don't need this marking now // that we've marked it as defaulted. - MD->setWillHaveBody(false); + MD->setWillHaveBody(Member == CXXComparisonOperator); // If this definition appears within the record, do the checking when // the record is complete. @@ -14344,10 +15107,31 @@ if (Primary->getCanonicalDecl()->isDefaulted()) return; - CheckExplicitlyDefaultedSpecialMember(MD); + CheckExplicitlyDefaultedMember(MD); if (!MD->isInvalidDecl()) DefineImplicitSpecialMember(*this, MD, DefaultLoc); + } else if (auto *FD = dyn_cast_or_null(Dcl)) { + if (FD->isDefaultComparisonOperator(true)) { + CXXRecordDecl *Parent = dyn_cast(FD->getLexicalParent()); + assert(!isa(Dcl)); + assert(Parent); + if (Parent->isDependentType()) { + FD->setDefaulted(); + FD->setExplicitlyDefaulted(); + return; + } + FD->setDefaulted(); + FD->setExplicitlyDefaulted(); + FD->setWillHaveBody(true); + + CheckExplicitlyDefaultedMember(FD); + + if (!FD->isInvalidDecl()) + DefineImplicitSpecialMember(*this, FD, DefaultLoc); + } else { + Diag(DefaultLoc, diag::err_default_special_members); + } } else { Diag(DefaultLoc, diag::err_default_special_members); } Index: lib/Sema/SemaExceptionSpec.cpp =================================================================== --- lib/Sema/SemaExceptionSpec.cpp +++ lib/Sema/SemaExceptionSpec.cpp @@ -182,7 +182,7 @@ // Compute or instantiate the exception specification now. if (SourceFPT->getExceptionSpecType() == EST_Unevaluated) - EvaluateImplicitExceptionSpec(Loc, cast(SourceDecl)); + EvaluateImplicitExceptionSpec(Loc, cast(SourceDecl)); else InstantiateExceptionSpec(Loc, SourceDecl); @@ -1210,6 +1210,8 @@ case Expr::VAArgExprClass: return canSubExprsThrow(*this, E); + case Expr::CXXRewrittenOperatorClass: + return canThrow(cast(E)->getRewrittenExpr()); // Some might be dependent for other reasons. case Expr::ArraySubscriptExprClass: case Expr::OMPArraySectionExprClass: Index: lib/Sema/SemaExpr.cpp =================================================================== --- lib/Sema/SemaExpr.cpp +++ lib/Sema/SemaExpr.cpp @@ -37,6 +37,7 @@ #include "clang/Sema/Designator.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/Overload.h" #include "clang/Sema/ParsedTemplate.h" #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" @@ -102,7 +103,8 @@ // Try to diagnose why this special member function was implicitly // deleted. This might fail, if that reason no longer applies. CXXSpecialMember CSM = getSpecialMember(Method); - if (CSM != CXXInvalid) + // FIXME(EricWF): Handle CXXComparisonOperator + if (CSM != CXXInvalid && CSM != CXXComparisonOperator) ShouldDeleteSpecialMember(Method, CSM, nullptr, /*Diagnose=*/true); return; @@ -9614,12 +9616,18 @@ Expr *RHSStripped = RHS->IgnoreParenImpCasts(); QualType LHSType = LHS->getType(); + QualType RHSType = RHS->getType(); if (LHSType->hasFloatingRepresentation() || (LHSType->isBlockPointerType() && !BinaryOperator::isEqualityOp(Opc)) || LHS->getLocStart().isMacroID() || RHS->getLocStart().isMacroID() || S.inTemplateInstantiation()) return; + // Comparisons between two array types are ill-formed for operator<=>, so + // we shouldn't emit any additional warnings about it. + if (Opc == BO_Cmp && LHSType->isArrayType() && RHSType->isArrayType()) + return; + // For non-floating point types, check for self-comparisons of the form // x == x, x != x, x < x, etc. These always evaluate to a constant, and // often indicate logic errors in the program. @@ -9703,10 +9711,153 @@ } } +static ImplicitConversionKind castKindToImplicitConversionKind(CastKind CK) { + switch (CK) { + default: + llvm_unreachable("unhandled cast kind"); + case CK_UserDefinedConversion: + return ICK_Identity; + case CK_LValueToRValue: + return ICK_Lvalue_To_Rvalue; + case CK_ArrayToPointerDecay: + return ICK_Array_To_Pointer; + case CK_FunctionToPointerDecay: + return ICK_Function_To_Pointer; + case CK_IntegralCast: + return ICK_Integral_Conversion; + case CK_FloatingCast: + return ICK_Floating_Conversion; + case CK_IntegralToFloating: + case CK_FloatingToIntegral: + return ICK_Floating_Integral; + } +} + +static bool checkThreeWayNarrowingConversion(Sema &S, QualType ToType, Expr *E, + QualType FromType, + SourceLocation Loc) { + // Check for a narrowing implicit conversion. + StandardConversionSequence SCS; + SCS.setToType(0, FromType); + SCS.setToType(1, ToType); + if (const auto *ICE = dyn_cast(E)) { + auto CastK = ICE->getCastKind(); + SCS.Second = castKindToImplicitConversionKind(CastK); + } + APValue PreNarrowingValue; + QualType PreNarrowingType; + switch (SCS.getNarrowingKind(S.Context, E, PreNarrowingValue, + PreNarrowingType, + /*IgnoreFloatToIntegralConversion*/ true)) { + case NK_Dependent_Narrowing: + // Implicit conversion to a narrower type, but the expression is + // value-dependent so we can't tell whether it's actually narrowing. + case NK_Not_Narrowing: + return false; + + case NK_Constant_Narrowing: + // Implicit conversion to a narrower type, and the value is not a constant + // expression. + S.Diag(E->getLocStart(), diag::err_spaceship_argument_narrowing) + << /*Constant*/ 1 + << PreNarrowingValue.getAsString(S.Context, PreNarrowingType) << ToType; + return true; + + case NK_Variable_Narrowing: + // Implicit conversion to a narrower type, and the value is not a constant + // expression. + case NK_Type_Narrowing: + S.Diag(E->getLocStart(), diag::err_spaceship_argument_narrowing) + << /*Constant*/ 0 << FromType << ToType; + // TODO: It's not a constant expression, but what if the user intended it + // to be? Can we produce notes to help them figure out why it isn't? + return true; + } + llvm_unreachable("unhandled case in switch"); +} + +static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S, + ExprResult &LHS, + ExprResult &RHS, + SourceLocation Loc) { + using CCT = ComparisonCategoryType; + + // Dig out the original argument type and expression before implicit casts + // were applied. These are the types/expressions we need to check the + // [expr.spaceship] requirements against. + LHS = LHS.get()->IgnoreParenImpCasts(); + RHS = RHS.get()->IgnoreParenImpCasts(); + QualType LHSType = LHS.get()->getType(); + QualType RHSType = RHS.get()->getType(); + + // C++2a [expr.spaceship]p3: If one of the operands is of type bool and the + // other is not, the program is ill-formed. + if (LHSType->isBooleanType() != RHSType->isBooleanType()) { + S.InvalidOperands(Loc, LHS, RHS); + return QualType(); + } + + int NumEnumArgs = (int)LHSType->isEnumeralType() + RHSType->isEnumeralType(); + if (NumEnumArgs == 1) { + bool LHSIsEnum = LHSType->isEnumeralType(); + QualType OtherTy = LHSIsEnum ? RHSType : LHSType; + if (OtherTy->hasFloatingRepresentation()) { + S.InvalidOperands(Loc, LHS, RHS); + return QualType(); + } + } + if (NumEnumArgs == 2) { + // C++2a [expr.spaceship]p5: If both operands have the same enumeration + // type E, the operator yields the result of converting the operands + // to the underlying type of E and applying <=> to the converted operands. + if (!S.Context.hasSameUnqualifiedType(LHSType, RHSType)) { + S.InvalidOperands(Loc, LHS, RHS); + return QualType(); + } + QualType IntType = LHSType->getAs()->getDecl()->getIntegerType(); + assert(IntType->isArithmeticType()); + + LHS = S.ImpCastExprToType(LHS.get(), IntType, CK_IntegralCast); + RHS = S.ImpCastExprToType(RHS.get(), IntType, CK_IntegralCast); + LHSType = RHSType = IntType; + } + + // C++2a [expr.spaceship]p4: If both operands have arithmetic types, the + // usual arithmetic conversions are applied to the operands. + QualType Type = S.UsualArithmeticConversions(LHS, RHS); + if (LHS.isInvalid() || RHS.isInvalid()) + return QualType(); + if (Type.isNull()) + return S.InvalidOperands(Loc, LHS, RHS); + assert(Type->isArithmeticType() || Type->isEnumeralType()); + + bool HasNarrowing = checkThreeWayNarrowingConversion( + S, Type, LHS.get(), LHSType, LHS.get()->getLocStart()); + HasNarrowing |= checkThreeWayNarrowingConversion( + S, Type, RHS.get(), RHSType, RHS.get()->getLocStart()); + if (HasNarrowing) + return QualType(); + + assert(!Type.isNull() && "composite type for <=> has not been set"); + + auto TypeKind = [&]() { + if (Type->isIntegralOrEnumerationType()) + return CCT::StrongOrdering; + if (Type->hasFloatingRepresentation()) + return CCT::PartialOrdering; + llvm_unreachable("other types are unimplemented"); + }(); + + return S.CheckComparisonCategoryType(TypeKind, Loc); +} + static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, BinaryOperatorKind Opc) { + if (Opc == BO_Cmp) + return checkArithmeticOrEnumeralThreeWayCompare(S, LHS, RHS, Loc); + // C99 6.5.8p3 / C99 6.5.9p4 QualType Type = S.UsualArithmeticConversions(LHS, RHS); if (LHS.isInvalid() || RHS.isInvalid()) @@ -9717,15 +9868,7 @@ checkEnumComparison(S, Loc, LHS.get(), RHS.get()); - enum { StrongEquality, PartialOrdering, StrongOrdering } Ordering; - if (Type->isAnyComplexType()) - Ordering = StrongEquality; - else if (Type->isFloatingType()) - Ordering = PartialOrdering; - else - Ordering = StrongOrdering; - - if (Ordering == StrongEquality && BinaryOperator::isRelationalOp(Opc)) + if (Type->isAnyComplexType() && BinaryOperator::isRelationalOp(Opc)) return S.InvalidOperands(Loc, LHS, RHS); // Check for comparisons of floating point operands using != and ==. @@ -9733,14 +9876,24 @@ S.CheckFloatComparison(Loc, LHS.get(), RHS.get()); // The result of comparisons is 'bool' in C++, 'int' in C. - // FIXME: For BO_Cmp, return the relevant comparison category type. return S.Context.getLogicalOperationType(); } // C99 6.5.8, C++ [expr.rel] QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, - SourceLocation Loc, BinaryOperatorKind Opc, - bool IsRelational) { + SourceLocation Loc, + BinaryOperatorKind Opc) { + bool IsRelational = BinaryOperator::isRelationalOp(Opc); + bool IsThreeWay = Opc == BO_Cmp; + auto IsAnyPointerType = [](ExprResult E) { + QualType Ty = E.get()->getType(); + return Ty->isPointerType() || Ty->isMemberPointerType(); + }; + + // C++2a [expr.spaceship]p6: If at least one of the operands is of pointer + // type, array-to-pointer, ..., conversions are performed on both operands to + // bring them to their composite type. + if (!IsThreeWay || IsAnyPointerType(LHS) || IsAnyPointerType(RHS)) { // Comparisons expect an rvalue, so convert to rvalue before any // type-related checks. LHS = DefaultFunctionArrayLvalueConversion(LHS.get()); @@ -9749,6 +9902,7 @@ RHS = DefaultFunctionArrayLvalueConversion(RHS.get()); if (RHS.isInvalid()) return QualType(); + } checkArithmeticNull(*this, LHS, RHS, Loc, /*isCompare=*/true); @@ -9766,8 +9920,6 @@ (RHSType->isArithmeticType() || RHSType->isEnumeralType())) return checkArithmeticOrEnumeralCompare(*this, LHS, RHS, Loc, Opc); - QualType ResultTy = Context.getLogicalOperationType(); - const Expr::NullPointerConstantKind LHSNullKind = LHS.get()->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull); const Expr::NullPointerConstantKind RHSNullKind = @@ -9775,6 +9927,44 @@ bool LHSIsNull = LHSNullKind != Expr::NPCK_NotNull; bool RHSIsNull = RHSNullKind != Expr::NPCK_NotNull; + auto computeResultTy = [&]() { + if (Opc != BO_Cmp) + return Context.getLogicalOperationType(); + assert(getLangOpts().CPlusPlus); + assert(Context.hasSameType(LHS.get()->getType(), RHS.get()->getType())); + + QualType CompositeTy = LHS.get()->getType(); + assert(!CompositeTy->isReferenceType()); + + auto buildResultTy = [&](ComparisonCategoryType Kind) { + return CheckComparisonCategoryType(Kind, Loc); + }; + + // C++2a [expr.spaceship]p7: If the composite pointer type is a function + // pointer type, a pointer-to-member type, or std::nullptr_t, the + // result is of type std::strong_equality + if (CompositeTy->isFunctionPointerType() || + CompositeTy->isMemberPointerType() || CompositeTy->isNullPtrType()) + // FIXME: consider making the function pointer case produce + // strong_ordering not strong_equality, per P0946R0-Jax18 discussion + // and direction polls + return buildResultTy(ComparisonCategoryType::StrongEquality); + + // C++2a [expr.spaceship]p8: If the composite pointer type is an object + // pointer type, p <=> q is of type std::strong_ordering. + if (CompositeTy->isPointerType()) { + // P0946R0: Comparisons between a null pointer constant and an object + // pointer result in std::strong_equality + if (LHSIsNull != RHSIsNull) + return buildResultTy(ComparisonCategoryType::StrongEquality); + return buildResultTy(ComparisonCategoryType::StrongOrdering); + } + // C++2a [expr.spaceship]p9: Otherwise, the program is ill-formed. + // TODO: Extend support for operator<=> to ObjC types. + return InvalidOperands(Loc, LHS, RHS); + }; + + if (!IsRelational && LHSIsNull != RHSIsNull) { bool IsEquality = Opc == BO_EQ; if (RHSIsNull) @@ -9807,24 +9997,25 @@ return QualType(); RHS = ImpCastExprToType(RHS.get(), LHSType, CK_BitCast); - return ResultTy; + return computeResultTy(); } // C++ [expr.eq]p2: // If at least one operand is a pointer [...] bring them to their // composite pointer type. + // C++ [expr.spaceship]p6 + // If at least one of the operands is of pointer type, [...] bring them + // to their composite pointer type. // C++ [expr.rel]p2: // If both operands are pointers, [...] bring them to their composite // pointer type. if ((int)LHSType->isPointerType() + (int)RHSType->isPointerType() >= (IsRelational ? 2 : 1) && - (!LangOpts.ObjCAutoRefCount || - !(LHSType->isObjCObjectPointerType() || + (!LangOpts.ObjCAutoRefCount || !(LHSType->isObjCObjectPointerType() || RHSType->isObjCObjectPointerType()))) { if (convertPointersToCompositeType(*this, Loc, LHS, RHS)) return QualType(); - else - return ResultTy; + return computeResultTy(); } } else if (LHSType->isPointerType() && RHSType->isPointerType()) { // C99 6.5.8p2 @@ -9875,7 +10066,7 @@ else RHS = ImpCastExprToType(RHS.get(), LHSType, Kind); } - return ResultTy; + return computeResultTy(); } if (getLangOpts().CPlusPlus) { @@ -9885,11 +10076,11 @@ if (!IsRelational && LHSIsNull && RHSIsNull) { if (LHSType->isNullPtrType()) { RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } if (RHSType->isNullPtrType()) { LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } } @@ -9898,12 +10089,12 @@ if (!IsRelational && RHSType->isNullPtrType() && (LHSType->isObjCObjectPointerType() || LHSType->isBlockPointerType())) { RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } if (!IsRelational && LHSType->isNullPtrType() && (RHSType->isObjCObjectPointerType() || RHSType->isBlockPointerType())) { LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } if (IsRelational && @@ -9926,7 +10117,7 @@ RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); else LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } } } @@ -9939,7 +10130,7 @@ if (convertPointersToCompositeType(*this, Loc, LHS, RHS)) return QualType(); else - return ResultTy; + return computeResultTy(); } } @@ -9956,7 +10147,7 @@ << RHS.get()->getSourceRange(); } RHS = ImpCastExprToType(RHS.get(), LHSType, CK_BitCast); - return ResultTy; + return computeResultTy(); } // Allow block pointers to be compared with null pointer constants. @@ -9980,7 +10171,7 @@ RHS = ImpCastExprToType(RHS.get(), LHSType, LHSType->isPointerType() ? CK_BitCast : CK_AnyPointerToBlockPointerCast); - return ResultTy; + return computeResultTy(); } if (LHSType->isObjCObjectPointerType() || @@ -10013,7 +10204,7 @@ RHS = ImpCastExprToType(E, LHSType, LPT ? CK_BitCast :CK_CPointerToObjCPointerCast); } - return ResultTy; + return computeResultTy(); } if (LHSType->isObjCObjectPointerType() && RHSType->isObjCObjectPointerType()) { @@ -10027,20 +10218,20 @@ LHS = ImpCastExprToType(LHS.get(), RHSType, CK_BitCast); else RHS = ImpCastExprToType(RHS.get(), LHSType, CK_BitCast); - return ResultTy; + return computeResultTy(); } if (!IsRelational && LHSType->isBlockPointerType() && RHSType->isBlockCompatibleObjCPointerType(Context)) { LHS = ImpCastExprToType(LHS.get(), RHSType, CK_BlockPointerToObjCPointerCast); - return ResultTy; + return computeResultTy(); } else if (!IsRelational && LHSType->isBlockCompatibleObjCPointerType(Context) && RHSType->isBlockPointerType()) { RHS = ImpCastExprToType(RHS.get(), LHSType, CK_BlockPointerToObjCPointerCast); - return ResultTy; + return computeResultTy(); } } if ((LHSType->isAnyPointerType() && RHSType->isIntegerType()) || @@ -10080,30 +10271,30 @@ else RHS = ImpCastExprToType(RHS.get(), LHSType, RHSIsNull ? CK_NullToPointer : CK_IntegralToPointer); - return ResultTy; + return computeResultTy(); } // Handle block pointers. if (!IsRelational && RHSIsNull && LHSType->isBlockPointerType() && RHSType->isIntegerType()) { RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } if (!IsRelational && LHSIsNull && LHSType->isIntegerType() && RHSType->isBlockPointerType()) { LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } if (getLangOpts().OpenCLVersion >= 200) { if (LHSIsNull && RHSType->isQueueT()) { LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } if (LHSType->isQueueT() && RHSIsNull) { RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } } @@ -11755,19 +11946,17 @@ case BO_GE: case BO_GT: ConvertHalfVec = true; - ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc, true); + ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc); break; case BO_EQ: case BO_NE: ConvertHalfVec = true; - ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc, false); + ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc); break; case BO_Cmp: - // FIXME: Implement proper semantic checking of '<=>'. ConvertHalfVec = true; - ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc, true); - if (!ResultTy.isNull()) - ResultTy = Context.VoidTy; + ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc); + assert(ResultTy.isNull() || ResultTy->getAsCXXRecordDecl()); break; case BO_And: checkObjCPointerIntrospection(*this, LHS, RHS, OpLoc); @@ -12141,7 +12330,12 @@ if (Sc && OverOp != OO_None && OverOp != OO_Equal) S.LookupOverloadedOperatorName(OverOp, Sc, LHS->getType(), RHS->getType(), Functions); - + if (S.getLangOpts().CPlusPlus2a) { + if (Sc && Opc != BO_Cmp && BinaryOperator::isRelationalOp(Opc)) { + S.LookupOverloadedOperatorName(OO_Spaceship, Sc, LHS->getType(), + RHS->getType(), Functions); + } + } // Build the (potentially-overloaded, potentially-dependent) // binary operation. return S.CreateOverloadedBinOp(OpLoc, Opc, Functions, LHS, RHS); @@ -13961,7 +14155,8 @@ static bool isImplicitlyDefinableConstexprFunction(FunctionDecl *Func) { CXXMethodDecl *MD = dyn_cast(Func); return Func->isConstexpr() && - (Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided())); + (Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided()) || + Func->isDefaultComparisonOperator()); } /// \brief Mark a function referenced, and check whether it is odr-used @@ -14051,13 +14246,16 @@ MarkVTableUsed(Loc, Destructor->getParent()); } else if (CXXMethodDecl *MethodDecl = dyn_cast(Func)) { if (MethodDecl->isOverloadedOperator() && - MethodDecl->getOverloadedOperator() == OO_Equal) { + (MethodDecl->getOverloadedOperator() == OO_Equal || + MethodDecl->isDefaultComparisonOperator())) { MethodDecl = cast(MethodDecl->getFirstDecl()); if (MethodDecl->isDefaulted() && !MethodDecl->isDeleted()) { if (MethodDecl->isCopyAssignmentOperator()) DefineImplicitCopyAssignment(Loc, MethodDecl); else if (MethodDecl->isMoveAssignmentOperator()) DefineImplicitMoveAssignment(Loc, MethodDecl); + else if (MethodDecl->isDefaultComparisonOperator()) + DefineDefaultedComparisonOperator(Loc, MethodDecl); } } else if (isa(MethodDecl) && MethodDecl->getParent()->isLambda()) { @@ -14069,7 +14267,8 @@ DefineImplicitLambdaToFunctionPointerConversion(Loc, Conversion); } else if (MethodDecl->isVirtual() && getLangOpts().AppleKext) MarkVTableUsed(Loc, MethodDecl->getParent()); - } + } else if (Func->isDefaultComparisonOperator()) + DefineDefaultedComparisonOperator(Loc, Func); // Recursive functions should be marked when used from another function. // FIXME: Is this really right? Index: lib/Sema/SemaLookup.cpp =================================================================== --- lib/Sema/SemaLookup.cpp +++ lib/Sema/SemaLookup.cpp @@ -2857,6 +2857,8 @@ bool RValueThis, bool ConstThis, bool VolatileThis) { + assert(SM != CXXComparisonOperator && + "lookups for comparisons should be handled elsewhere"); assert(CanDeclareSpecialMemberFunction(RD) && "doing special member lookup into record that isn't fully complete"); RD = RD->getDefinition(); @@ -2896,7 +2898,7 @@ DeclareImplicitDestructor(RD); CXXDestructorDecl *DD = RD->getDestructor(); assert(DD && "record without a destructor"); - Result->setMethod(DD); + Result->setFunction(DD); Result->setKind(DD->isDeleted() ? SpecialMemberOverloadResult::NoMemberOrDeleted : SpecialMemberOverloadResult::Success); @@ -2981,7 +2983,7 @@ // destructor. assert(SM == CXXDefaultConstructor && "lookup for a constructor or assignment operator was empty"); - Result->setMethod(nullptr); + Result->setFunction(nullptr); Result->setKind(SpecialMemberOverloadResult::NoMemberOrDeleted); return *Result; } @@ -3028,22 +3030,22 @@ OverloadCandidateSet::iterator Best; switch (OCS.BestViableFunction(*this, LookupLoc, Best)) { case OR_Success: - Result->setMethod(cast(Best->Function)); + Result->setFunction(Best->Function); Result->setKind(SpecialMemberOverloadResult::Success); break; case OR_Deleted: - Result->setMethod(cast(Best->Function)); + Result->setFunction(Best->Function); Result->setKind(SpecialMemberOverloadResult::NoMemberOrDeleted); break; case OR_Ambiguous: - Result->setMethod(nullptr); + Result->setFunction(nullptr); Result->setKind(SpecialMemberOverloadResult::Ambiguous); break; case OR_No_Viable_Function: - Result->setMethod(nullptr); + Result->setFunction(nullptr); Result->setKind(SpecialMemberOverloadResult::NoMemberOrDeleted); break; } @@ -3057,7 +3059,7 @@ LookupSpecialMember(Class, CXXDefaultConstructor, false, false, false, false, false); - return cast_or_null(Result.getMethod()); + return cast_or_null(Result.getFunction()); } /// \brief Look up the copying constructor for the given class. @@ -3069,7 +3071,7 @@ LookupSpecialMember(Class, CXXCopyConstructor, Quals & Qualifiers::Const, Quals & Qualifiers::Volatile, false, false, false); - return cast_or_null(Result.getMethod()); + return cast_or_null(Result.getFunction()); } /// \brief Look up the moving constructor for the given class. @@ -3079,7 +3081,7 @@ LookupSpecialMember(Class, CXXMoveConstructor, Quals & Qualifiers::Const, Quals & Qualifiers::Volatile, false, false, false); - return cast_or_null(Result.getMethod()); + return cast_or_null(Result.getFunction()); } /// \brief Look up the constructors for the given class. @@ -3113,7 +3115,7 @@ ThisQuals & Qualifiers::Const, ThisQuals & Qualifiers::Volatile); - return Result.getMethod(); + return cast_or_null(Result.getFunction()); } /// \brief Look up the moving assignment operator for the given class. @@ -3129,7 +3131,7 @@ ThisQuals & Qualifiers::Const, ThisQuals & Qualifiers::Volatile); - return Result.getMethod(); + return cast_or_null(Result.getFunction()); } /// \brief Look for the destructor of the given class. @@ -3140,8 +3142,100 @@ /// \returns The destructor for this class. CXXDestructorDecl *Sema::LookupDestructor(CXXRecordDecl *Class) { return cast(LookupSpecialMember(Class, CXXDestructor, - false, false, false, - false, false).getMethod()); + false, false, false, false, + false) + .getFunction()); +} + +Sema::SpecialMemberOverloadResult +Sema::LookupThreeWayComparison(CXXRecordDecl *RD, QualType ArgType, + SourceLocation Loc) { + using ResultKind = SpecialMemberOverloadResultEntry::Kind; + assert(!ArgType->isReferenceType() && + "expression cannot have reference types"); + + // Add a const qualifier before constructing the node ID to prevent + // unnecessary duplicates. + ArgType.addConst(); + + // FIXME(EricWF): Before we do expensive lookup, check if we have + // non-overloadable types (ie arithmetic, pointer, member pointer, ect) + // In these cases we can deduce the builtin type without performing + // overload resolution. + + // FIXME(EricWF): It's not clear if we should even be caching lookups for + // defaulted comparison operators, since unlike special members, the lookup + // results can change. For now we add the record decl doing the lookup to + // avoid this. + llvm::FoldingSetNodeID ID; + ID.AddPointer(RD); + // FIXME(EricWF): Should we remove the quals before canonicalizing the type? + ID.AddPointer(ArgType.getAsOpaquePtr()); + ID.AddInteger(CXXComparisonOperator); + + void *InsertPoint; + SpecialMemberOverloadResultEntry *Result = + SpecialMemberCache.FindNodeOrInsertPos(ID, InsertPoint); + + // This was already cached + if (Result) + return *Result; + + Result = BumpAlloc.Allocate(); + Result = new (Result) SpecialMemberOverloadResultEntry(ID); + SpecialMemberCache.InsertNode(Result, InsertPoint); + + DeclarationName Name = + Context.DeclarationNames.getCXXOperatorName(OO_Spaceship); + + OpaqueValueExpr FakeArg(Loc, ArgType, VK_LValue); + Expr *Args[2] = {&FakeArg, &FakeArg}; + + // FIXME(EricWF): Do this earlier, and pass in the result. + UnresolvedSet<16> Functions; + LookupOverloadedOperatorName(OO_Spaceship, getCurScope(), ArgType, ArgType, + Functions); + + OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Operator); + // Add the candidates from the given function set. + AddFunctionCandidates(Functions, Args, CandidateSet); + + // Add operator candidates that are member functions. + AddMemberOperatorCandidates(OO_Spaceship, Loc, Args, CandidateSet); + + // FIXME(EricWF): Should we be performing ADL here? Seems likely. + AddArgumentDependentLookupCandidates(Name, Loc, Args, + /*ExplicitTemplateArgs*/ nullptr, + CandidateSet); + // Add builtin operator candidates. + AddBuiltinOperatorCandidates(OO_Spaceship, Loc, Args, CandidateSet); + + // FIXME(EricWF): Ignore rewritten candidates for now. + + OverloadCandidateSet::iterator Best; + switch (CandidateSet.BestViableFunction(*this, Loc, Best)) { + case OR_Success: { + OverloadCandidate &Ovl = *Best; + Result->setKind(ResultKind::Success); + if (Ovl.Function) + Result->setFunction(Ovl.Function); + else { + assert(Ovl.getNumParams() == 2 && + Context.hasSameType(Ovl.BuiltinParamTypes[0], + Ovl.BuiltinParamTypes[1])); + Result->setBuiltinArgType(Ovl.BuiltinParamTypes[0]); + } + break; + } + case OR_Ambiguous: + Result->setKind(ResultKind::Ambiguous); + break; + case OR_Deleted: + case OR_No_Viable_Function: + Result->setKind(ResultKind::NoMemberOrDeleted); + break; + } + return *Result; } /// LookupLiteralOperator - Determine which literal operator should be used for Index: lib/Sema/SemaOverload.cpp =================================================================== --- lib/Sema/SemaOverload.cpp +++ lib/Sema/SemaOverload.cpp @@ -288,11 +288,11 @@ /// value of the expression prior to the narrowing conversion. /// \param ConstantType If this is an NK_Constant_Narrowing conversion, the /// type of the expression prior to the narrowing conversion. -NarrowingKind -StandardConversionSequence::getNarrowingKind(ASTContext &Ctx, - const Expr *Converted, - APValue &ConstantValue, - QualType &ConstantType) const { +/// \param IgnoreFloatToIntegralConversion If true type-narrowing conversions +/// from floating point types to integral types should be ignored. +NarrowingKind StandardConversionSequence::getNarrowingKind( + ASTContext &Ctx, const Expr *Converted, APValue &ConstantValue, + QualType &ConstantType, bool IgnoreFloatToIntegralConversion) const { assert(Ctx.getLangOpts().CPlusPlus && "narrowing check outside C++"); // C++11 [dcl.init.list]p7: @@ -329,6 +329,8 @@ return NK_Type_Narrowing; } else if (FromType->isIntegralOrUnscopedEnumerationType() && ToType->isRealFloatingType()) { + if (IgnoreFloatToIntegralConversion) + return NK_Not_Narrowing; llvm::APSInt IntConstantValue; const Expr *Initializer = IgnoreNarrowingConversion(Converted); assert(Initializer && "Unknown conversion expression"); @@ -829,6 +831,19 @@ } } +const ImplicitConversionSequence & +OverloadCandidate::getConversion(unsigned ArgIdx) const { + return Conversions[getConversionIndexForArgIndex(ArgIdx)]; +} + +unsigned OverloadCandidate::getConversionIndexForArgIndex(unsigned Idx) const { + if (getRewrittenKind() != ROC_Synthesized) + return Idx; + // FIXME(EricWF): Handle these cases. + assert(Idx < 2); + return Idx == 0 ? 1 : 0; +} + void OverloadCandidateSet::destroyCandidates() { for (iterator i = begin(), e = end(); i != e; ++i) { for (auto &C : i->Conversions) @@ -5940,13 +5955,15 @@ // (possibly cv-qualified) T2", when T2 is an enumeration type, are // candidate functions. if (CandidateSet.getKind() == OverloadCandidateSet::CSK_Operator && - !IsAcceptableNonMemberOperatorCandidate(Context, Function, Args)) + !IsAcceptableNonMemberOperatorCandidate(Context, Function, Args)) { return; + } // C++11 [class.copy]p11: [DR1402] // A defaulted move constructor that is defined as deleted is ignored by // overload resolution. CXXConstructorDecl *Constructor = dyn_cast(Function); + if (Constructor && Constructor->isDefaulted() && Constructor->isDeleted() && Constructor->isMoveConstructor()) return; @@ -5962,6 +5979,7 @@ Candidate.Function = Function; Candidate.Viable = true; Candidate.IsSurrogate = false; + Candidate.RewrittenOpKind = ROC_None; Candidate.IgnoreObjectArgument = false; Candidate.ExplicitCallArguments = Args.size(); @@ -6652,6 +6670,7 @@ Candidate.Function = MethodTmpl->getTemplatedDecl(); Candidate.Viable = false; Candidate.IsSurrogate = false; + Candidate.RewrittenOpKind = ROC_None; Candidate.IgnoreObjectArgument = cast(Candidate.Function)->isStatic() || ObjectType.isNull(); @@ -6716,6 +6735,7 @@ Candidate.Function = FunctionTemplate->getTemplatedDecl(); Candidate.Viable = false; Candidate.IsSurrogate = false; + Candidate.RewrittenOpKind = ROC_None; // Ignore the object argument if there is one, since we don't have an object // type. Candidate.IgnoreObjectArgument = @@ -6887,6 +6907,7 @@ Candidate.FoundDecl = FoundDecl; Candidate.Function = Conversion; Candidate.IsSurrogate = false; + Candidate.RewrittenOpKind = ROC_None; Candidate.IgnoreObjectArgument = false; Candidate.FinalConversion.setAsIdentityConversion(); Candidate.FinalConversion.setFromType(ConvType); @@ -7048,6 +7069,7 @@ Candidate.Viable = false; Candidate.FailureKind = ovl_fail_bad_deduction; Candidate.IsSurrogate = false; + Candidate.RewrittenOpKind = ROC_None; Candidate.IgnoreObjectArgument = false; Candidate.ExplicitCallArguments = 1; Candidate.DeductionFailure = MakeDeductionFailureInfo(Context, Result, @@ -7088,6 +7110,7 @@ Candidate.Surrogate = Conversion; Candidate.Viable = true; Candidate.IsSurrogate = true; + Candidate.RewrittenOpKind = ROC_None; Candidate.IgnoreObjectArgument = false; Candidate.ExplicitCallArguments = Args.size(); @@ -7245,6 +7268,7 @@ Candidate.FoundDecl = DeclAccessPair::make(nullptr, AS_none); Candidate.Function = nullptr; Candidate.IsSurrogate = false; + Candidate.RewrittenOpKind = ROC_None; Candidate.IgnoreObjectArgument = false; std::copy(ParamTys, ParamTys + Args.size(), Candidate.BuiltinParamTypes); @@ -7986,7 +8010,8 @@ // bool operator>=(T, T); // bool operator==(T, T); // bool operator!=(T, T); - void addRelationalPointerOrEnumeralOverloads() { + // R operator<=>(T, T) + void addGenericBinaryPointerOrEnumeralOverloads() { // C++ [over.match.oper]p3: // [...]the built-in candidates include all of the candidate operator // functions defined in 13.6 that, compared to the given operator, [...] @@ -8059,7 +8084,6 @@ UserDefinedBinaryOperators.count(std::make_pair(CanonType, CanonType))) continue; - QualType ParamTypes[2] = { *Enum, *Enum }; S.AddBuiltinCandidate(ParamTypes, Args, CandidateSet); } @@ -8177,6 +8201,41 @@ } } + // C++2a [over.built]p14: + // + // For every integral type T there exists a candidate operator function + // of the form + // + // std::strong_ordering operator<=>(T, T) + // + // C++2a [over.built]p15: + // + // For every pair of floating-point types L and R, there exists a candidate + // operator function of the form + // + // std::partial_ordering operator<=>(L, R); + // + // FIXME: The current specification for integral types doesn't play nice with + // the direction of p0946r0, which allows mixed integral and unscoped-enum + // comparisons. Under the current spec this can lead to ambiguity during + // overload resolution. For example: + // + // enum A : int {a}; + // auto x = (a <=> (long)42); + // + // error: call is ambiguous for arguments 'A' and 'long'. + // note: candidate operator<=>(int, int) + // note: candidate operator<=>(long, long) + // + // To avoid this error, this function deviates from the specification and adds + // the mixed overloads `operator<=>(L, R)` where L and R are promoted + // arithmetic types (the same as the generic relational overloads). + // + // For now this function acts as a placeholder. + void addThreeWayArithmeticOverloads() { + addGenericBinaryArithmeticOverloads(); + } + // C++ [over.built]p17: // // For every pair of promoted integral types L and R, there @@ -8745,12 +8804,14 @@ case OO_Greater: case OO_LessEqual: case OO_GreaterEqual: - OpBuilder.addRelationalPointerOrEnumeralOverloads(); + OpBuilder.addGenericBinaryPointerOrEnumeralOverloads(); OpBuilder.addGenericBinaryArithmeticOverloads(); break; case OO_Spaceship: - llvm_unreachable("<=> expressions not supported yet"); + OpBuilder.addGenericBinaryPointerOrEnumeralOverloads(); + OpBuilder.addThreeWayArithmeticOverloads(); + break; case OO_Percent: case OO_Caret: @@ -8821,6 +8882,67 @@ } } +/// Add the rewritten and synthesized candidates for binary comparison +/// operators. No additional semantic checking is done to see if the candidate +/// is well formed. +void Sema::AddRewrittenOperatorCandidates(OverloadedOperatorKind Op, + SourceLocation OpLoc, + ArrayRef InputArgs, + const UnresolvedSetImpl &Fns, + OverloadCandidateSet &CandidateSet, + bool PerformADL) { + auto Opc = BinaryOperator::getOverloadedOpcode(Op); + + bool IsRelationalOrEquality = + BinaryOperator::isRelationalOp(Opc) || BinaryOperator::isEqualityOp(Opc); + if (!IsRelationalOrEquality && Opc != BO_Cmp) + return; + assert(InputArgs.size() == 2); + + OverloadedOperatorKind CmpOp = OO_Spaceship; + DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(CmpOp); + + // AddCandidates - Add operator<=> candidates for the specified set of args, + // and mark all newly generated candidates as having the specified + // 'RewrittenOverloadCandidateKind'. + auto AddCandidates = [&](ArrayRef Args, + RewrittenOverloadCandidateKind Kind) { + OverloadCandidateSet::RewrittenCandidateContextGuard Guard(CandidateSet); + + unsigned InitialSize = CandidateSet.size(); + AddFunctionCandidates(Fns, Args, CandidateSet); + AddMemberOperatorCandidates(CmpOp, OpLoc, Args, CandidateSet); + if (PerformADL) + AddArgumentDependentLookupCandidates(OpName, OpLoc, Args, + /*ExplicitTemplateArgs*/ nullptr, + CandidateSet); + AddBuiltinOperatorCandidates(CmpOp, OpLoc, Args, CandidateSet); + + for (auto It = std::next(CandidateSet.begin(), InitialSize); + It != CandidateSet.end(); ++It) { + OverloadCandidate &Ovl = *It; + Ovl.RewrittenOpKind = Kind; + } + }; + + // If we have a relational or equality operation, add the rewritten candidates + // of the form: (LHS <=> RHS) @ 0 + if (IsRelationalOrEquality) + AddCandidates(InputArgs, ROC_Rewritten); + + // TODO: We should be able to avoid adding synthesized candidates when LHS and + // RHS have the same type, value category, and other relevent properties. + // In that case synthesized candidates for <=> should be the same as the + // rewritten ones. Note: It's still possible for the result of operator<=> to + // be usable only on the left or right side of the expression (0 @ ) + // or ( @ 0). + + // For relational, equality, and three-way comparisons, add the rewritten and + // synthesized candidates of the form: 0 @ (RHS <=> LHS) + SmallVector ReverseArgs(InputArgs.rbegin(), InputArgs.rend()); + AddCandidates(ReverseArgs, ROC_Synthesized); +} + /// \brief Add function candidates found via argument-dependent lookup /// to the set of overloading candidates. /// @@ -8828,13 +8950,10 @@ /// given function name (which may also be an operator name) and adds /// all of the overload candidates found by ADL to the overload /// candidate set (C++ [basic.lookup.argdep]). -void -Sema::AddArgumentDependentLookupCandidates(DeclarationName Name, - SourceLocation Loc, - ArrayRef Args, +void Sema::AddArgumentDependentLookupCandidates( + DeclarationName Name, SourceLocation Loc, ArrayRef Args, TemplateArgumentListInfo *ExplicitTemplateArgs, - OverloadCandidateSet& CandidateSet, - bool PartialOverloading) { + OverloadCandidateSet &CandidateSet, bool PartialOverloading) { ADLResult Fns; // FIXME: This approach for uniquing ADL results (and removing @@ -8967,8 +9086,8 @@ assert(Cand2.Conversions.size() == NumArgs && "Overload candidate mismatch"); bool HasBetterConversion = false; for (unsigned ArgIdx = StartArg; ArgIdx < NumArgs; ++ArgIdx) { - bool Cand1Bad = IsIllFormedConversion(Cand1.Conversions[ArgIdx]); - bool Cand2Bad = IsIllFormedConversion(Cand2.Conversions[ArgIdx]); + bool Cand1Bad = IsIllFormedConversion(Cand1.getConversion(ArgIdx)); + bool Cand2Bad = IsIllFormedConversion(Cand2.getConversion(ArgIdx)); if (Cand1Bad != Cand2Bad) { if (Cand1Bad) return false; @@ -8984,9 +9103,8 @@ // viable function F2 if for all arguments i, ICSi(F1) is not a worse // conversion sequence than ICSi(F2), and then... for (unsigned ArgIdx = StartArg; ArgIdx < NumArgs; ++ArgIdx) { - switch (CompareImplicitConversionSequences(S, Loc, - Cand1.Conversions[ArgIdx], - Cand2.Conversions[ArgIdx])) { + switch (CompareImplicitConversionSequences( + S, Loc, Cand1.getConversion(ArgIdx), Cand2.getConversion(ArgIdx))) { case ImplicitConversionSequence::Better: // Cand1 has a better conversion sequence. HasBetterConversion = true; @@ -9092,6 +9210,32 @@ // Inherited from sibling base classes: still ambiguous. } + // Check C++2a tie-breakers for rewritten candidates + { + // --- F2 is a rewritten candidate ([over.match.oper]) and F1 is not. + RewrittenOverloadCandidateKind C1Roc = Cand1.getRewrittenKind(); + RewrittenOverloadCandidateKind C2Roc = Cand2.getRewrittenKind(); + if (C1Roc || C2Roc) { + if (!C1Roc || !C2Roc) + return !C1Roc; + // --- F1 and F2 are rewritten candidates, and F2 is a synthesized + // candidate with reversed order of parameters and F1 is not. + if ((C1Roc == ROC_Synthesized || C2Roc == ROC_Synthesized) && + C1Roc != C2Roc) { + auto GetParamTypes = [&](const OverloadCandidate &Ovl) { + SmallVector Types; + for (unsigned I = 0; I < Ovl.getNumParams(); ++I) + Types.push_back(Ovl.getParamType(I).getCanonicalType()); + if (Ovl.getRewrittenKind() == ROC_Synthesized) + llvm::reverse(Types); + return Types; + }; + if (GetParamTypes(Cand1) == GetParamTypes(Cand2)) + return C2Roc == ROC_Synthesized; + } + } + } + // Check C++17 tie-breakers for deduction guides. { auto *Guide1 = dyn_cast_or_null(Cand1.Function); @@ -9204,9 +9348,9 @@ /// function, \p Best points to the candidate function found. /// /// \returns The result of overload resolution. -OverloadingResult -OverloadCandidateSet::BestViableFunction(Sema &S, SourceLocation Loc, - iterator &Best) { +OverloadingResult OverloadCandidateSet::BestViableFunction( + Sema &S, SourceLocation Loc, iterator &Best, + SmallVectorImpl *EquivalentCands) { llvm::SmallVector Candidates; std::transform(begin(), end(), std::back_inserter(Candidates), [](OverloadCandidate &Cand) { return &Cand; }); @@ -9248,23 +9392,32 @@ if (Best == end()) return OR_No_Viable_Function; - llvm::SmallVector EquivalentCands; - + llvm::SmallVector EquivalentFunctionCands; + if (EquivalentCands) + EquivalentCands->push_back(&(*Best)); // Make sure that this function is better than every other viable // function. If not, we have an ambiguity. for (auto *Cand : Candidates) { if (Cand->Viable && Cand != Best && !isBetterOverloadCandidate(S, *Best, *Cand, Loc, Kind)) { + if (EquivalentCands) + EquivalentCands->push_back(Cand); if (S.isEquivalentInternalLinkageDeclaration(Best->Function, Cand->Function)) { - EquivalentCands.push_back(Cand->Function); + EquivalentFunctionCands.push_back(Cand->Function); continue; } + if (EquivalentCands) + continue; Best = end(); return OR_Ambiguous; } } + if (EquivalentCands && EquivalentCands->size() > 1) { + Best = end(); + return OR_Ambiguous; + } // Best is the best viable function. if (Best->Function && @@ -9272,9 +9425,9 @@ S.isFunctionConsideredUnavailable(Best->Function))) return OR_Deleted; - if (!EquivalentCands.empty()) + if (!EquivalentFunctionCands.empty()) S.diagnoseEquivalentInternalLinkageDeclarations(Loc, Best->Function, - EquivalentCands); + EquivalentFunctionCands); return OR_Success; } @@ -12187,6 +12340,313 @@ return CreateBuiltinUnaryOp(OpLoc, Opc, Input); } +static ExprResult buildBinaryOperatorCandidate(Sema &S, SourceLocation OpLoc, + BinaryOperatorKind Opc, + const OverloadCandidate &Ovl, + Expr *LHSE, Expr *RHSE, + bool HadMultipleCandidates) { + Expr *Args[2] = {LHSE, RHSE}; + OverloadedOperatorKind Op = BinaryOperator::getOverloadedOperator(Opc); + // We found a built-in operator or an overloaded operator. + FunctionDecl *FnDecl = Ovl.Function; + + if (FnDecl) { + Expr *Base = nullptr; + // We matched an overloaded operator. Build a call to that + // operator. + + // Convert the arguments. + if (CXXMethodDecl *Method = dyn_cast(FnDecl)) { + // Ovl.Access is only meaningful for class members. + S.CheckMemberOperatorAccess(OpLoc, Args[0], Args[1], Ovl.FoundDecl); + + ExprResult Arg1 = + S.PerformCopyInitialization(InitializedEntity::InitializeParameter( + S.Context, FnDecl->getParamDecl(0)), + SourceLocation(), Args[1]); + if (Arg1.isInvalid()) + return ExprError(); + + ExprResult Arg0 = S.PerformObjectArgumentInitialization( + Args[0], /*Qualifier=*/nullptr, Ovl.FoundDecl, Method); + if (Arg0.isInvalid()) + return ExprError(); + Base = Args[0] = Arg0.getAs(); + Args[1] = Arg1.getAs(); + } else { + // Convert the arguments. + ExprResult Arg0 = + S.PerformCopyInitialization(InitializedEntity::InitializeParameter( + S.Context, FnDecl->getParamDecl(0)), + SourceLocation(), Args[0]); + if (Arg0.isInvalid()) + return ExprError(); + + ExprResult Arg1 = + S.PerformCopyInitialization(InitializedEntity::InitializeParameter( + S.Context, FnDecl->getParamDecl(1)), + SourceLocation(), Args[1]); + if (Arg1.isInvalid()) + return ExprError(); + Args[0] = Arg0.getAs(); + Args[1] = Arg1.getAs(); + } + + // Build the actual expression node. + ExprResult FnExpr = CreateFunctionRefExpr(S, FnDecl, Ovl.FoundDecl, Base, + HadMultipleCandidates, OpLoc); + if (FnExpr.isInvalid()) + return ExprError(); + + // Determine the result type. + QualType ResultTy = FnDecl->getReturnType(); + ExprValueKind VK = Expr::getValueKindForType(ResultTy); + ResultTy = ResultTy.getNonLValueExprType(S.Context); + + CXXOperatorCallExpr *TheCall = new (S.Context) CXXOperatorCallExpr( + S.Context, Op, FnExpr.get(), Args, ResultTy, VK, OpLoc, S.FPFeatures); + + if (S.CheckCallReturnType(FnDecl->getReturnType(), OpLoc, TheCall, FnDecl)) + return ExprError(); + + ArrayRef ArgsArray(Args, 2); + const Expr *ImplicitThis = nullptr; + // Cut off the implicit 'this'. + if (isa(FnDecl)) { + ImplicitThis = ArgsArray[0]; + ArgsArray = ArgsArray.slice(1); + } + + // Check for a self move. + if (Op == OO_Equal) + S.DiagnoseSelfMove(Args[0], Args[1], OpLoc); + + S.checkCall(FnDecl, nullptr, ImplicitThis, ArgsArray, + isa(FnDecl), OpLoc, TheCall->getSourceRange(), + Sema::VariadicDoesNotApply); + + return S.MaybeBindToTemporary(TheCall); + + } else { + // We matched a built-in operator. Convert the arguments, then + // break out so that we will build the appropriate built-in + // operator node. + ExprResult ArgsRes0 = + S.PerformImplicitConversion(Args[0], Ovl.BuiltinParamTypes[0], + Ovl.Conversions[0], Sema::AA_Passing); + if (ArgsRes0.isInvalid()) + return ExprError(); + Args[0] = ArgsRes0.get(); + + ExprResult ArgsRes1 = + S.PerformImplicitConversion(Args[1], Ovl.BuiltinParamTypes[1], + Ovl.Conversions[1], Sema::AA_Passing); + if (ArgsRes1.isInvalid()) + return ExprError(); + Args[1] = ArgsRes1.get(); + } + // We matched a built-in operator; build it. + return S.CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]); +} + +namespace { + +/// \brief RewrittenOverloadResolver - This class handles initial +/// overload resolution for candidate sets which include rewritten candidates. +/// +/// Rewritten candidates haven't been fully checked for validity. They may still +/// be invalid if: +/// (A) The rewritten candidate is a builtin, but semantic checking of the +/// builtin would fail. +/// (B) The result of the "partially rewritten expression" +/// (ie the (LHS <=> RHS) part) is ill-formed when used as an operand to +/// ( @ 0) or (0 @ ). +/// +/// TODO: Separate out the bits of semantic checking for builtin spaceship +/// operators which determine validity and the return type, and use that instead +/// of building the full expression to check validity. +class RewrittenOverloadResolver { + enum CheckOverloadResult { Done, Continue }; + +public: + RewrittenOverloadResolver(Sema &S, SourceLocation OpLoc, + BinaryOperatorKind Opc, ArrayRef Args, + const UnresolvedSetImpl &Fns, bool PerformADL, + OverloadCandidateSet &CS) + : S(S), OpLoc(OpLoc), Opc(Opc), Args(Args), Fns(Fns), + PerformADL(PerformADL), CandidateSet(CS) {} + + ExprResult ResolveRewrittenCandidates() { + ExprResult FinalResult = ExprError(); + OverloadCandidateSet::iterator Best; + OverloadingResult OvlRes; + llvm::SmallVector EquivCands; + do { + EquivCands.clear(); + OvlRes = CandidateSet.BestViableFunction(S, OpLoc, Best, &EquivCands); + } while (Continue == RemoveNonViableRewrittenCandidates( + OvlRes, Best, EquivCands, FinalResult)); + return FinalResult; + } +private: + CheckOverloadResult RemoveNonViableRewrittenCandidates( + OverloadingResult OvlRes, OverloadCandidateSet::iterator Best, + ArrayRef EquivCands, ExprResult &FinalResult); + ExprResult BuildRewrittenCandidate(const OverloadCandidate &Ovl); + + RewrittenOverloadResolver(RewrittenOverloadResolver const &) = delete; + RewrittenOverloadResolver & + operator=(RewrittenOverloadResolver const &) = delete; + +private: + Sema &S; + SourceLocation OpLoc; + BinaryOperatorKind Opc; + ArrayRef Args; + const UnresolvedSetImpl &Fns; + bool PerformADL; + OverloadCandidateSet &CandidateSet; +}; +} // end namespace + +ExprResult RewrittenOverloadResolver::BuildRewrittenCandidate( + const OverloadCandidate &Ovl) { + Expr *RewrittenArgs[2] = {Args[0], Args[1]}; + assert(Ovl.getRewrittenKind()); + bool IsSynthesized = Ovl.getRewrittenKind() == ROC_Synthesized; + if (IsSynthesized) + std::swap(RewrittenArgs[0], RewrittenArgs[1]); + + ExprResult RewrittenRes = ExprError(); + + // Supress diagnostics when building the expressions for the specified + // candidate. If evaluation fails the candidate will be marked non-viable + // and the best viable candidate re-computed. + Sema::TentativeAnalysisScope DiagnosticScopeGuard(S); + + // Build the '(LHS <=> RHS)' operand to the full expression. + RewrittenRes = buildBinaryOperatorCandidate( + S, OpLoc, BO_Cmp, Ovl, RewrittenArgs[0], RewrittenArgs[1], + /*HadMultipleCandidates*/ false); + if (RewrittenRes.isInvalid()) + return ExprError(); + + // Now attempt to build the full expression '(LHS <=> RHS) @ 0' using the + // evaluated operand and the literal 0. + llvm::APInt I = + llvm::APInt::getNullValue(S.Context.getIntWidth(S.Context.IntTy)); + Expr *Zero = + IntegerLiteral::Create(S.Context, I, S.Context.IntTy, SourceLocation()); + + Expr *NewLHS = RewrittenRes.get(); + Expr *NewRHS = Zero; + if (Ovl.getRewrittenKind() == ROC_Synthesized) + std::swap(NewLHS, NewRHS); + + ExprResult FinalRes = + S.CreateOverloadedBinOp(OpLoc, Opc, Fns, NewLHS, NewRHS, PerformADL, + /*AllowRewrittenCandidates*/ false); + if (FinalRes.isInvalid()) + return ExprError(); + return new (S.Context) CXXRewrittenOperator( + (CXXRewrittenOperator::RewrittenOperatorKind)Ovl.getRewrittenKind(), + RewrittenRes.get(), FinalRes.get()); +} + +/// Rewritten candidates have been added but not checked for validity. They +/// could still be non-viable if: +/// (A) The rewritten call (x <=> y) is a builtin, but it will be ill-formed +/// when built (for example it has narrowing conversions). +/// (B) The expression (x <=> y) @ 0 is ill-formed for the result of (x <=> y). +/// +/// If either is the case, this function should be considered non-viable and +/// another best viable function needs to be computed. +/// +/// Therefore, we do the following: +/// (1) If we have no viable candidate, or a deleted candidate, stop. +/// Otherwise, if we have a best viable candidate or a set of ambiguous +/// candidates and none of them are rewritten, stop. +/// +/// (2) If the best viable candidate is a rewritten candidate, build and +/// check the full expression for that candidate. If it succeeds return +/// the result. Otherwise, mark the candidate as non-viable, re-compute +/// the best viable function, and continue. +/// +/// (3) If we have ambiguity attempt to resolve it by evaluating each rewritten +/// candidate causing ambiguity: +/// +/// (3.1) build the full expression for the specified candidate. +/// (3.2) If the result is invalid, mark the candidate as non-viable. +/// +/// If only one viable candidate remains, stop. If the viable candidate is +/// rewritten, return the previously computed full expression. Otherwise, +/// if we have more than one viable candidate, stop. If no viable candidates +/// remain from the initial set of equally ranked candidates, recompute the +/// new best viable overload and continue. +RewrittenOverloadResolver::CheckOverloadResult +RewrittenOverloadResolver::RemoveNonViableRewrittenCandidates( + OverloadingResult OvlRes, OverloadCandidateSet::iterator Best, + ArrayRef EquivCands, ExprResult &FinalResult) { + auto Success = [&](ExprResult Res) { + FinalResult = Res; + return Done; + }; + switch (OvlRes) { + case OR_Deleted: + // FIXME(EricWF): If we've found a deleted rewritten operator, it's + // possible we should have never considered it a viable candidate. + case OR_No_Viable_Function: + return Done; + + case OR_Success: { + OverloadCandidate &Ovl = *Best; + if (!Ovl.getRewrittenKind()) + return Done; + // Build the full expression for the rewritten candidate, and return it if + // it's valid. Otherwise mark this candidate as non-viable and continue. + ExprResult Res = BuildRewrittenCandidate(Ovl); + if (Res.isInvalid()) { + Ovl.Viable = false; + return Continue; + } + return Success(Res); + } + case OR_Ambiguous: { + assert(EquivCands.size() > 1); + // If none of the viable candidates are rewritten, stop. + if (llvm::none_of(EquivCands, [](const OverloadCandidate *Cand) { + return (bool)Cand->getRewrittenKind(); + })) + return Done; + int NumViableCandidates = 0; + ExprResult ViableRewritten = ExprError(); + for (auto *Cand : EquivCands) { + OverloadCandidate &Ovl = *Cand; + if (Ovl.getRewrittenKind()) { + ExprResult Res = BuildRewrittenCandidate(Ovl); + if (Res.isInvalid()) { + Ovl.Viable = false; + continue; + } + ViableRewritten = Res; + } + ++NumViableCandidates; + } + // If only one of the candidates turns out to be viable, and it's a + // rewritten candidate, return that candidate as the result. + if (NumViableCandidates == 1 && !ViableRewritten.isInvalid()) + return Success(ViableRewritten); + // If we still have ambiguity, return and let the caller diagnose it. + if (NumViableCandidates > 1) + return Done; + // All of the equally ranked candidates are invalid. Loop and try overload + // resolution again. + return Continue; + } + } + llvm_unreachable("unhandled case"); +} + /// \brief Create a binary operation that may resolve to an overloaded /// operator. /// @@ -12203,11 +12663,11 @@ /// /// \param LHS Left-hand argument. /// \param RHS Right-hand argument. -ExprResult -Sema::CreateOverloadedBinOp(SourceLocation OpLoc, +ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, BinaryOperatorKind Opc, - const UnresolvedSetImpl &Fns, - Expr *LHS, Expr *RHS, bool PerformADL) { + 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 @@ -12269,11 +12729,27 @@ if (Opc == BO_PtrMemD) return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]); + UnresolvedSet<6> OrigFuncs; + UnresolvedSet<6> ThreeWayFuncs; + for (NamedDecl *D : Fns) { + FunctionDecl *FD = D->getAsFunction(); + if (FD) { + assert(FD->isOverloadedOperator()); + if (FD->getOverloadedOperator() == OO_Spaceship) { + ThreeWayFuncs.addDecl(D); + if (Op == OO_Spaceship) + OrigFuncs.addDecl(D); + } else + OrigFuncs.addDecl(D); + } else + OrigFuncs.addDecl(D); + } + // Build an empty overload set. OverloadCandidateSet CandidateSet(OpLoc, OverloadCandidateSet::CSK_Operator); // Add the candidates from the given function set. - AddFunctionCandidates(Fns, Args, CandidateSet); + AddFunctionCandidates(OrigFuncs, Args, CandidateSet); // Add operator candidates that are member functions. AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet); @@ -12289,119 +12765,43 @@ // Add builtin operator candidates. AddBuiltinOperatorCandidates(Op, OpLoc, Args, CandidateSet); - bool HadMultipleCandidates = (CandidateSet.size() > 1); - - // Perform overload resolution. - OverloadCandidateSet::iterator Best; - switch (CandidateSet.BestViableFunction(*this, OpLoc, Best)) { - case OR_Success: { - // We found a built-in operator or an overloaded operator. - FunctionDecl *FnDecl = Best->Function; - - if (FnDecl) { - Expr *Base = nullptr; - // We matched an overloaded operator. Build a call to that - // operator. - - // Convert the arguments. - if (CXXMethodDecl *Method = dyn_cast(FnDecl)) { - // Best->Access is only meaningful for class members. - CheckMemberOperatorAccess(OpLoc, Args[0], Args[1], Best->FoundDecl); - - ExprResult Arg1 = - PerformCopyInitialization( - InitializedEntity::InitializeParameter(Context, - FnDecl->getParamDecl(0)), - SourceLocation(), Args[1]); - if (Arg1.isInvalid()) - return ExprError(); - - ExprResult Arg0 = - PerformObjectArgumentInitialization(Args[0], /*Qualifier=*/nullptr, - Best->FoundDecl, Method); - if (Arg0.isInvalid()) - return ExprError(); - Base = Args[0] = Arg0.getAs(); - Args[1] = RHS = Arg1.getAs(); - } else { - // Convert the arguments. - ExprResult Arg0 = PerformCopyInitialization( - InitializedEntity::InitializeParameter(Context, - FnDecl->getParamDecl(0)), - SourceLocation(), Args[0]); - if (Arg0.isInvalid()) - return ExprError(); - - ExprResult Arg1 = - PerformCopyInitialization( - InitializedEntity::InitializeParameter(Context, - FnDecl->getParamDecl(1)), - SourceLocation(), Args[1]); - if (Arg1.isInvalid()) - return ExprError(); - Args[0] = LHS = Arg0.getAs(); - Args[1] = RHS = Arg1.getAs(); + // C++2a Add rewritten and synthesized operator candidates. + bool HasRewrittenCandidates = false; + if (getLangOpts().CPlusPlus2a && AllowRewrittenCandidates && + BinaryOperator::isComparisonOp(Opc)) { + unsigned BeforeRewrittenSize = CandidateSet.size(); + AddRewrittenOperatorCandidates(Op, OpLoc, Args, ThreeWayFuncs, CandidateSet, + PerformADL); + HasRewrittenCandidates = BeforeRewrittenSize != CandidateSet.size(); } - // Build the actual expression node. - ExprResult FnExpr = CreateFunctionRefExpr(*this, FnDecl, - Best->FoundDecl, Base, - HadMultipleCandidates, OpLoc); - if (FnExpr.isInvalid()) - return ExprError(); - - // Determine the result type. - QualType ResultTy = FnDecl->getReturnType(); - ExprValueKind VK = Expr::getValueKindForType(ResultTy); - ResultTy = ResultTy.getNonLValueExprType(Context); + if (HasRewrittenCandidates) { + RewrittenOverloadResolver RewrittenOvlResolver(*this, OpLoc, Opc, Args, Fns, + PerformADL, CandidateSet); - CXXOperatorCallExpr *TheCall = - new (Context) CXXOperatorCallExpr(Context, Op, FnExpr.get(), - Args, ResultTy, VK, OpLoc, - FPFeatures); - - if (CheckCallReturnType(FnDecl->getReturnType(), OpLoc, TheCall, - FnDecl)) - return ExprError(); + // Perform initial overload resolution that includes partially checked + // rewritten candidates, removing rewritten candidates which turn out to be + // invalid as needed. + ExprResult RewrittenResult = + RewrittenOvlResolver.ResolveRewrittenCandidates(); - ArrayRef ArgsArray(Args, 2); - const Expr *ImplicitThis = nullptr; - // Cut off the implicit 'this'. - if (isa(FnDecl)) { - ImplicitThis = ArgsArray[0]; - ArgsArray = ArgsArray.slice(1); - } - - // Check for a self move. - if (Op == OO_Equal) - DiagnoseSelfMove(Args[0], Args[1], OpLoc); - - checkCall(FnDecl, nullptr, ImplicitThis, ArgsArray, - isa(FnDecl), OpLoc, TheCall->getSourceRange(), - VariadicDoesNotApply); - - return MaybeBindToTemporary(TheCall); - } else { - // We matched a built-in operator. Convert the arguments, then - // break out so that we will build the appropriate built-in - // operator node. - ExprResult ArgsRes0 = - PerformImplicitConversion(Args[0], Best->BuiltinParamTypes[0], - Best->Conversions[0], AA_Passing); - if (ArgsRes0.isInvalid()) - return ExprError(); - Args[0] = ArgsRes0.get(); - - ExprResult ArgsRes1 = - PerformImplicitConversion(Args[1], Best->BuiltinParamTypes[1], - Best->Conversions[1], AA_Passing); - if (ArgsRes1.isInvalid()) - return ExprError(); - Args[1] = ArgsRes1.get(); - break; - } + // If overload resolution was successful and the result was a re-written + // overload candidate, then that candidate was evaluated and we can return + // the result directly. + if (!RewrittenResult.isInvalid()) + return RewrittenResult; } + // Perform final overload resolution. + bool HadMultipleCandidates = (CandidateSet.size() > 1); + OverloadCandidateSet::iterator Best; + switch (CandidateSet.BestViableFunction(*this, OpLoc, Best)) { + case OR_Success: + assert( + !Best->getRewrittenKind() && + "rewritten candidates should have already been resolved and evaluated"); + return buildBinaryOperatorCandidate(*this, OpLoc, Opc, *Best, Args[0], + Args[1], HadMultipleCandidates); case OR_No_Viable_Function: { // C++ [over.match.oper]p9: // If the operator is the operator , [...] and there are no @@ -12414,15 +12814,15 @@ // operator do not fall through to handling in built-in, but report that // no overloaded assignment operator found ExprResult Result = ExprError(); - if (Args[0]->getType()->isRecordType() && - Opc >= BO_Assign && Opc <= BO_OrAssign) { + if (Args[0]->getType()->isRecordType() && Opc >= BO_Assign && + Opc <= BO_OrAssign) { Diag(OpLoc, diag::err_ovl_no_viable_oper) - << BinaryOperator::getOpcodeStr(Opc) - << Args[0]->getSourceRange() << Args[1]->getSourceRange(); + << BinaryOperator::getOpcodeStr(Opc) << Args[0]->getSourceRange() + << Args[1]->getSourceRange(); if (Args[0]->getType()->isIncompleteType()) { Diag(OpLoc, diag::note_assign_lhs_incomplete) - << Args[0]->getType() - << Args[0]->getSourceRange() << Args[1]->getSourceRange(); + << Args[0]->getType() << Args[0]->getSourceRange() + << Args[1]->getSourceRange(); } } else { // This is an erroneous use of an operator which can be overloaded by @@ -12443,12 +12843,11 @@ BinaryOperator::getOpcodeStr(Opc), OpLoc); return Result; } - case OR_Ambiguous: Diag(OpLoc, diag::err_ovl_ambiguous_oper_binary) - << BinaryOperator::getOpcodeStr(Opc) - << Args[0]->getType() << Args[1]->getType() - << Args[0]->getSourceRange() << Args[1]->getSourceRange(); + << BinaryOperator::getOpcodeStr(Opc) << Args[0]->getType() + << Args[1]->getType() << Args[0]->getSourceRange() + << Args[1]->getSourceRange(); CandidateSet.NoteCandidates(*this, OCD_ViableCandidates, Args, BinaryOperator::getOpcodeStr(Opc), OpLoc); return ExprError(); @@ -12466,8 +12865,7 @@ return ExprError(); } else { Diag(OpLoc, diag::err_ovl_deleted_oper) - << Best->Function->isDeleted() - << BinaryOperator::getOpcodeStr(Opc) + << Best->Function->isDeleted() << BinaryOperator::getOpcodeStr(Opc) << getDeletedOrUnavailableSuffix(Best->Function) << Args[0]->getSourceRange() << Args[1]->getSourceRange(); } Index: lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiateDecl.cpp +++ lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1853,7 +1853,11 @@ PrincipalDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary)) PrincipalDecl->setNonMemberOperator(); - assert(!D->isDefaulted() && "only methods should be defaulted"); + if (D->isExplicitlyDefaulted()) { + assert(D->isDefaultComparisonOperator() && + "Only default comparison operators may be defaulted"); + SemaRef.SetDeclDefaulted(Function, Function->getLocation()); + } return Function; } Index: lib/Sema/TreeTransform.h =================================================================== --- lib/Sema/TreeTransform.h +++ lib/Sema/TreeTransform.h @@ -3217,6 +3217,13 @@ return getSema().BuildEmptyCXXFoldExpr(EllipsisLoc, Operator); } + ExprResult + RebuildCXXRewrittenOperator(CXXRewrittenOperator::RewrittenOperatorKind Kind, + Expr *Underlying, Expr *Rewritten) { + return new (SemaRef.Context) + CXXRewrittenOperator(Kind, Underlying, Rewritten); + } + /// \brief Build a new atomic operation expression. /// /// By default, performs semantic analysis to build the new expression. @@ -11524,6 +11531,28 @@ template ExprResult +TreeTransform::TransformCXXRewrittenOperator(CXXRewrittenOperator *E) { + ExprResult Orig = getDerived().TransformExpr(E->getUnderlyingExpr()); + if (Orig.isInvalid()) + return ExprError(); + + // FIXME(EricWF): Is there a case where the underlying expression has been + // transformed in such a way that we need to re-compute the rewritten + // expression? (and not just re-build it). + ExprResult Rewritten = getDerived().TransformExpr(E->getRewrittenExpr()); + if (Rewritten.isInvalid()) + return ExprError(); + + if (getDerived().AlwaysRebuild() || Orig.get() != E->getUnderlyingExpr() || + Rewritten.get() != E->getRewrittenExpr()) { + return getDerived().RebuildCXXRewrittenOperator(E->getKind(), Orig.get(), + Rewritten.get()); + } + return E; +} + +template +ExprResult TreeTransform::TransformCXXFoldExpr(CXXFoldExpr *E) { Expr *Pattern = E->getPattern(); Index: lib/Serialization/ASTReaderStmt.cpp =================================================================== --- lib/Serialization/ASTReaderStmt.cpp +++ lib/Serialization/ASTReaderStmt.cpp @@ -1705,6 +1705,13 @@ E->Opcode = (BinaryOperatorKind)Record.readInt(); } +void ASTStmtReader::VisitCXXRewrittenOperator(CXXRewrittenOperator *E) { + VisitExpr(E); + E->Kind = (CXXRewrittenOperator::RewrittenOperatorKind)Record.readInt(); + E->SubExprs[0] = Record.readSubExpr(); + E->SubExprs[1] = Record.readSubExpr(); +} + void ASTStmtReader::VisitOpaqueValueExpr(OpaqueValueExpr *E) { VisitExpr(E); E->SourceExpr = Record.readSubExpr(); @@ -4074,6 +4081,9 @@ S = new (Context) CXXFoldExpr(Empty); break; + case EXPR_CXX_REWRITTEN_OPERATOR: + S = new (Context) CXXRewrittenOperator(Empty); + case EXPR_OPAQUE_VALUE: S = new (Context) OpaqueValueExpr(Empty); break; Index: lib/Serialization/ASTWriterStmt.cpp =================================================================== --- lib/Serialization/ASTWriterStmt.cpp +++ lib/Serialization/ASTWriterStmt.cpp @@ -1695,6 +1695,14 @@ Code = serialization::EXPR_CXX_FOLD; } +void ASTStmtWriter::VisitCXXRewrittenOperator(CXXRewrittenOperator *E) { + VisitExpr(E); + Record.push_back(E->Kind); + Record.AddStmt(E->SubExprs[0]); + Record.AddStmt(E->SubExprs[1]); + Code = serialization::EXPR_CXX_REWRITTEN_OPERATOR; +} + void ASTStmtWriter::VisitOpaqueValueExpr(OpaqueValueExpr *E) { VisitExpr(E); Record.AddStmt(E->getSourceExpr()); Index: lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngine.cpp +++ lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1285,6 +1285,7 @@ case Stmt::PackExpansionExprClass: case Stmt::SubstNonTypeTemplateParmPackExprClass: case Stmt::FunctionParmPackExprClass: + case Stmt::CXXRewrittenOperatorClass: case Stmt::CoroutineBodyStmtClass: case Stmt::CoawaitExprClass: case Stmt::DependentCoawaitExprClass: Index: test/CodeGenCXX/Inputs/std-compare.h =================================================================== --- /dev/null +++ test/CodeGenCXX/Inputs/std-compare.h @@ -0,0 +1,437 @@ +#ifndef STD_COMPARE_H +#define STD_COMPARE_H + +namespace std { +inline namespace __1 { + +// exposition only +enum class _EqResult : unsigned char { + __zero = 0, + __equal = __zero, + __equiv = __equal, + __nonequal = 1, + __nonequiv = __nonequal +}; + +enum class _OrdResult : signed char { + __less = -1, + __greater = 1 +}; + +enum class _NCmpResult : signed char { + __unordered = -127 +}; + +struct _CmpUnspecifiedType; +using _CmpUnspecifiedParam = void (_CmpUnspecifiedType::*)(); + +class weak_equality { + constexpr explicit weak_equality(_EqResult __val) noexcept : __value_(__val) {} + +public: + static const weak_equality equivalent; + static const weak_equality nonequivalent; + + friend constexpr bool operator==(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, weak_equality __v) noexcept; + friend constexpr bool operator!=(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, weak_equality __v) noexcept; + friend constexpr weak_equality operator<=>(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr weak_equality operator<=>(_CmpUnspecifiedParam, weak_equality __v) noexcept; + + // test helper + constexpr bool test_eq(weak_equality const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _EqResult __value_; +}; + +inline constexpr weak_equality weak_equality::equivalent(_EqResult::__equiv); +inline constexpr weak_equality weak_equality::nonequivalent(_EqResult::__nonequiv); +inline constexpr bool operator==(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == _EqResult::__zero; +} +inline constexpr bool operator==(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v.__value_ == _EqResult::__zero; +} +inline constexpr bool operator!=(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != _EqResult::__zero; +} +inline constexpr bool operator!=(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v.__value_ != _EqResult::__zero; +} + +inline constexpr weak_equality operator<=>(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +inline constexpr weak_equality operator<=>(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v; +} + +class strong_equality { + explicit constexpr strong_equality(_EqResult __val) noexcept : __value_(__val) {} + +public: + static const strong_equality equal; + static const strong_equality nonequal; + static const strong_equality equivalent; + static const strong_equality nonequivalent; + + // conversion + constexpr operator weak_equality() const noexcept { + return __value_ == _EqResult::__zero ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + + // comparisons + friend constexpr bool operator==(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, strong_equality __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, strong_equality __v) noexcept; + + friend constexpr strong_equality operator<=>(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr strong_equality operator<=>(_CmpUnspecifiedParam, strong_equality __v) noexcept; + + // test helper + constexpr bool test_eq(strong_equality const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _EqResult __value_; +}; + +inline constexpr strong_equality strong_equality::equal(_EqResult::__equal); +inline constexpr strong_equality strong_equality::nonequal(_EqResult::__nonequal); +inline constexpr strong_equality strong_equality::equivalent(_EqResult::__equiv); +inline constexpr strong_equality strong_equality::nonequivalent(_EqResult::__nonequiv); +constexpr bool operator==(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == _EqResult::__zero; +} +constexpr bool operator==(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v.__value_ == _EqResult::__zero; +} +constexpr bool operator!=(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != _EqResult::__zero; +} +constexpr bool operator!=(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v.__value_ != _EqResult::__zero; +} + +constexpr strong_equality operator<=>(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr strong_equality operator<=>(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v; +} + +class partial_ordering { + using _ValueT = signed char; + explicit constexpr partial_ordering(_EqResult __v) noexcept + : __value_(_ValueT(__v)) {} + explicit constexpr partial_ordering(_OrdResult __v) noexcept + : __value_(_ValueT(__v)) {} + explicit constexpr partial_ordering(_NCmpResult __v) noexcept + : __value_(_ValueT(__v)) {} + + constexpr bool __is_ordered() const noexcept { + return __value_ != _ValueT(_NCmpResult::__unordered); + } + +public: + // valid values + static const partial_ordering less; + static const partial_ordering equivalent; + static const partial_ordering greater; + static const partial_ordering unordered; + + // conversion + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent : weak_equality::nonequivalent; + } + + // comparisons + friend constexpr bool operator==(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + + friend constexpr partial_ordering operator<=>(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr partial_ordering operator<=>(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(partial_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr partial_ordering partial_ordering::less(_OrdResult::__less); +inline constexpr partial_ordering partial_ordering::equivalent(_EqResult::__equiv); +inline constexpr partial_ordering partial_ordering::greater(_OrdResult::__greater); +inline constexpr partial_ordering partial_ordering::unordered(_NCmpResult ::__unordered); +constexpr bool operator==(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ == 0; +} +constexpr bool operator<(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ < 0; +} +constexpr bool operator<=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ <= 0; +} +constexpr bool operator>(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ > 0; +} +constexpr bool operator>=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 == __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 >= __v.__value_; +} +constexpr bool operator!=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return !__v.__is_ordered() || __v.__value_ != 0; +} +constexpr bool operator!=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return !__v.__is_ordered() || __v.__value_ != 0; +} + +constexpr partial_ordering operator<=>(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr partial_ordering operator<=>(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v < 0 ? partial_ordering::greater : (__v > 0 ? partial_ordering::less : __v); +} + +class weak_ordering { + using _ValueT = signed char; + explicit constexpr weak_ordering(_EqResult __v) noexcept : __value_(_ValueT(__v)) {} + explicit constexpr weak_ordering(_OrdResult __v) noexcept : __value_(_ValueT(__v)) {} + +public: + static const weak_ordering less; + static const weak_ordering equivalent; + static const weak_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + constexpr operator partial_ordering() const noexcept { + return __value_ == 0 ? partial_ordering::equivalent + : (__value_ < 0 ? partial_ordering::less : partial_ordering::greater); + } + + // comparisons + friend constexpr bool operator==(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + + friend constexpr weak_ordering operator<=>(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr weak_ordering operator<=>(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(weak_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr weak_ordering weak_ordering::less(_OrdResult::__less); +inline constexpr weak_ordering weak_ordering::equivalent(_EqResult::__equiv); +inline constexpr weak_ordering weak_ordering::greater(_OrdResult::__greater); +constexpr bool operator==(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == 0; +} +constexpr bool operator!=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != 0; +} +constexpr bool operator<(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ < 0; +} +constexpr bool operator<=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ <= 0; +} +constexpr bool operator>(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ > 0; +} +constexpr bool operator>=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 == __v.__value_; +} +constexpr bool operator!=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 != __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 >= __v.__value_; +} + +constexpr weak_ordering operator<=>(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr weak_ordering operator<=>(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return __v < 0 ? weak_ordering::greater : (__v > 0 ? weak_ordering::less : __v); +} + +class strong_ordering { + using _ValueT = signed char; + explicit constexpr strong_ordering(_EqResult __v) noexcept : __value_(static_cast(__v)) {} + explicit constexpr strong_ordering(_OrdResult __v) noexcept : __value_(static_cast(__v)) {} + +public: + static const strong_ordering less; + static const strong_ordering equal; + static const strong_ordering equivalent; + static const strong_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + constexpr operator strong_equality() const noexcept { + return __value_ == 0 ? strong_equality::equal + : strong_equality::nonequal; + } + constexpr operator partial_ordering() const noexcept { + return __value_ == 0 ? partial_ordering::equivalent + : (__value_ < 0 ? partial_ordering::less : partial_ordering::greater); + } + constexpr operator weak_ordering() const noexcept { + return __value_ == 0 ? weak_ordering::equivalent + : (__value_ < 0 ? weak_ordering::less : weak_ordering::greater); + } + + // comparisons + friend constexpr bool operator==(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + + friend constexpr strong_ordering operator<=>(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr strong_ordering operator<=>(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(strong_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr strong_ordering strong_ordering::less(_OrdResult::__less); +inline constexpr strong_ordering strong_ordering::equal(_EqResult::__equal); +inline constexpr strong_ordering strong_ordering::equivalent(_EqResult::__equiv); +inline constexpr strong_ordering strong_ordering::greater(_OrdResult::__greater); + +constexpr bool operator==(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == 0; +} +constexpr bool operator!=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != 0; +} +constexpr bool operator<(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ < 0; +} +constexpr bool operator<=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ <= 0; +} +constexpr bool operator>(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ > 0; +} +constexpr bool operator>=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 == __v.__value_; +} +constexpr bool operator!=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 != __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 >= __v.__value_; +} + +constexpr strong_ordering operator<=>(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr strong_ordering operator<=>(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return __v < 0 ? strong_ordering::greater : (__v > 0 ? strong_ordering::less : __v); +} + +// named comparison functions +constexpr bool is_eq(weak_equality __cmp) noexcept { return __cmp == 0; } +constexpr bool is_neq(weak_equality __cmp) noexcept { return __cmp != 0; } +constexpr bool is_lt(partial_ordering __cmp) noexcept { return __cmp < 0; } +constexpr bool is_lteq(partial_ordering __cmp) noexcept { return __cmp <= 0; } +constexpr bool is_gt(partial_ordering __cmp) noexcept { return __cmp > 0; } +constexpr bool is_gteq(partial_ordering __cmp) noexcept { return __cmp >= 0; } + +} // namespace __1 +} // end namespace std + +#endif // STD_COMPARE_H Index: test/CodeGenCXX/cxx2a-compare.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/cxx2a-compare.cpp @@ -0,0 +1,192 @@ +// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | \ +// RUN: FileCheck %s \ +// RUN: '-DSO="class.std::__1::strong_ordering"' \ +// RUN: -DSO_EQ=_ZNSt3__115strong_ordering5equalE \ +// RUN: -DSO_LT=_ZNSt3__115strong_ordering4lessE \ +// RUN: -DSO_GT=_ZNSt3__115strong_ordering7greaterE \ +// RUN: '-DSE="class.std::__1::strong_equality"' \ +// RUN: -DSE_EQ=_ZNSt3__115strong_equality5equalE \ +// RUN: -DSE_NE=_ZNSt3__115strong_equality8nonequalE \ +// RUN: '-DPO="class.std::__1::partial_ordering"' \ +// RUN: -DPO_EQ=_ZNSt3__116partial_ordering10equivalentE \ +// RUN: -DPO_LT=_ZNSt3__116partial_ordering4lessE \ +// RUN: -DPO_GT=_ZNSt3__116partial_ordering7greaterE \ +// RUN: -DPO_UNORD=_ZNSt3__116partial_ordering9unorderedE + +#include "Inputs/std-compare.h" + +typedef int INT; + +// CHECK-LABEL: @_Z11test_signedii +auto test_signed(int x, int y) { + // CHECK: %retval = alloca %[[SO]] + // CHECK: %cmp.lt = icmp slt i32 %0, %1 + // CHECK: %sel.lt = select i1 %cmp.lt, %[[SO]]* @[[SO_LT]], %[[SO]]* @[[SO_GT]] + // CHECK: %cmp.eq = icmp eq i32 %0, %1 + // CHECK: %sel.eq = select i1 %cmp.eq, %[[SO]]* @[[SO_EQ]], %[[SO]]* %sel.lt + // CHECK: %[[TMPRET:.*]] = bitcast %[[SO]]* %retval + // CHECK: %[[TMPSRC:.*]] = bitcast %[[SO]]* %sel.eq + // CHECK: call void @llvm.memcpy{{.*}}(i8* align 1 %[[TMPRET]], i8* align 1 %[[TMPSRC]] + // CHECK: ret + return x <=> y; +} + +// CHECK-LABEL: @_Z13test_unsignedjj +auto test_unsigned(unsigned x, unsigned y) { + // CHECK: %cmp.lt = icmp ult i32 %0, %1 + // CHECK: %sel.lt = select i1 %cmp.lt, %[[SO]]* @[[SO_LT]], %[[SO]]* @[[SO_GT]] + // CHECK: %cmp.eq = icmp eq i32 %0, %1 + // CHECK: %sel.eq = select i1 %cmp.eq, %[[SO]]* @[[SO_EQ]], %[[SO]]* %sel.lt + // CHECK: %retval + // CHECK: %sel.eq + // CHECK: ret + return x <=> y; +} + +// CHECK-LABEL: @_Z10float_testdd +auto float_test(double x, double y) { + // CHECK: %retval = alloca %[[PO]] + // CHECK: %cmp.eq = fcmp oeq double %0, %1 + // CHECK: %sel.eq = select i1 %cmp.eq, %[[PO]]* @[[PO_EQ]], %[[PO]]* @[[PO_UNORD]] + // CHECK: %cmp.gt = fcmp ogt double %0, %1 + // CHECK: %sel.gt = select i1 %cmp.gt, %[[PO]]* @[[PO_GT]], %[[PO]]* %sel.eq + // CHECK: %cmp.lt = fcmp olt double %0, %1 + // CHECK: %sel.lt = select i1 %cmp.lt, %[[PO]]* @[[PO_LT]], %[[PO]]* %sel.gt + // CHECK: %retval + // CHECK: %sel.lt + // CHECK: ret + return x <=> y; +} + +// CHECK-LABEL: @_Z8ptr_testPiS_ +auto ptr_test(int *x, int *y) { + // CHECK: %cmp.lt = icmp ult i32* %0, %1 + // CHECK: %sel.lt = select i1 %cmp.lt, %[[SO]]* @[[SO_LT]], %[[SO]]* @[[SO_GT]] + // CHECK: %cmp.eq = icmp eq i32* %0, %1 + // CHECK: %sel.eq = select i1 %cmp.eq, %[[SO]]* @[[SO_EQ]], %[[SO]]* %sel.lt + // CHECK: %retval + // CHECK: %sel.eq + // CHECK: ret + return x <=> y; +} + +struct MemPtr {}; +using MemPtrT = void (MemPtr::*)(); +using MemDataT = int(MemPtr::*); + +// CHECK-LABEL: @_Z12mem_ptr_testM6MemPtrFvvES1_ +auto mem_ptr_test(MemPtrT x, MemPtrT y) { + // CHECK: %retval = alloca %[[SE]] + // CHECK: %cmp.ptr = icmp eq i64 %lhs.memptr.ptr, %rhs.memptr.ptr + // CHECK: %cmp.ptr.null = icmp eq i64 %lhs.memptr.ptr, 0 + // CHECK: %cmp.adj = icmp eq i64 %lhs.memptr.adj, %rhs.memptr.adj + // CHECK: %6 = or i1 %cmp.ptr.null, %cmp.adj + // CHECK: %memptr.eq = and i1 %cmp.ptr, %6 + // CHECK: %sel.eq = select i1 %memptr.eq, %[[SE]]* @[[SE_EQ]], %[[SE]]* @[[SE_NE]] + // CHECK: %retval + // CHECK: %sel.eq + // CHECK: ret + return x <=> y; +} + +// CHECK-LABEL: @_Z13mem_data_testM6MemPtriS0_ +auto mem_data_test(MemDataT x, MemDataT y) { + // CHECK: %retval = alloca %[[SE]] + // CHECK: %[[CMP:.*]] = icmp eq i64 %0, %1 + // CHECK: %sel.eq = select i1 %[[CMP]], %[[SE]]* @[[SE_EQ]], %[[SE]]* @[[SE_NE]] + // CHECK: %retval + // CHECK: %sel.eq + return x <=> y; +} + +// CHECK-LABEL: @_Z13test_constantv +auto test_constant() { + // CHECK: entry: + // CHECK-NOT: icmp + // CHECK-NOT: [[SO_GT]] + // CHECK-NOT: [[SO_EQ]] + // CHECK: memcpy{{.*}}[[SO_LT]] + // CHECK: ret + const int x = 42; + const int y = 101; + return x <=> y; +} + +// CHECK-LABEL: @_Z16test_nullptr_objPiDn +auto test_nullptr_obj(int* x, decltype(nullptr) y) { + // CHECK: %retval = alloca %[[SE]] + // CHECK: %cmp.eq = icmp eq i32* %0, null + // CHECK: %sel.eq = select i1 %cmp.eq, %[[SE]]* @[[SE_EQ]], %[[SE]]* @[[SE_NE]] + // CHECK: %retval + // CHECK: %sel.eq + return x <=> y; +} + +// CHECK-LABEL: @_Z18unscoped_enum_testijlm +void unscoped_enum_test(int i, unsigned u, long l, unsigned long ul) { + enum EnumA : int { A }; + enum EnumB : unsigned { B }; + // CHECK: %[[I:.*]] = load {{.*}} %i.addr + // CHECK: icmp slt i32 {{.*}} %[[I]] + (void)(A <=> i); + + // CHECK: %[[U:.*]] = load {{.*}} %u.addr + // CHECK: icmp ult i32 {{.*}} %[[U]] + (void)(A <=> u); + + // CHECK: %[[L:.*]] = load {{.*}} %l.addr + // CHECK: icmp slt i64 {{.*}} %[[L]] + (void)(A <=> l); + + // CHECK: %[[U2:.*]] = load {{.*}} %u.addr + // CHECK: icmp ult i32 {{.*}} %[[U2]] + (void)(B <=> u); + + // CHECK: %[[UL:.*]] = load {{.*}} %ul.addr + // CHECK: icmp ult i64 {{.*}} %[[UL]] + (void)(B <=> ul); +} + +namespace RewrittenTest { +struct U { + int x; + std::strong_ordering operator<=>(U const &) const; +}; +struct T : public U { + int x, y; + auto operator<=>(T const &) const = default; +}; + +// FIXME(EricWF): Write this test +auto test(T t1, T t2) { + return (t1 < t2); +} + +} // namespace RewrittenTest + +namespace DefaultTest { +struct T { + int x, y, z; + std::strong_ordering operator<=>(T const &) const; +}; +struct U { + float y; + friend std::partial_ordering operator<=>(U const &, U const &); +}; +struct TU : public T, public U { + friend auto operator<=>(TU const &, TU const &) = default; +}; + +// CHECK: FIXME(EricWF) Write this test +void test1(TU x, TU y) { + (void)(x <=> y); +} + +struct Empty { + auto operator<=>(Empty const &) const = default; +}; + +auto test_empty(Empty t1, Empty t2) { + return t1 <=> t2; +} +} // namespace DefaultTest Index: test/PCH/Inputs/std-compare.h =================================================================== --- /dev/null +++ test/PCH/Inputs/std-compare.h @@ -0,0 +1,437 @@ +#ifndef STD_COMPARE_H +#define STD_COMPARE_H + +namespace std { +inline namespace __1 { + +// exposition only +enum class _EqResult : unsigned char { + __zero = 0, + __equal = __zero, + __equiv = __equal, + __nonequal = 1, + __nonequiv = __nonequal +}; + +enum class _OrdResult : signed char { + __less = -1, + __greater = 1 +}; + +enum class _NCmpResult : signed char { + __unordered = -127 +}; + +struct _CmpUnspecifiedType; +using _CmpUnspecifiedParam = void (_CmpUnspecifiedType::*)(); + +class weak_equality { + constexpr explicit weak_equality(_EqResult __val) noexcept : __value_(__val) {} + +public: + static const weak_equality equivalent; + static const weak_equality nonequivalent; + + friend constexpr bool operator==(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, weak_equality __v) noexcept; + friend constexpr bool operator!=(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, weak_equality __v) noexcept; + friend constexpr weak_equality operator<=>(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr weak_equality operator<=>(_CmpUnspecifiedParam, weak_equality __v) noexcept; + + // test helper + constexpr bool test_eq(weak_equality const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _EqResult __value_; +}; + +inline constexpr weak_equality weak_equality::equivalent(_EqResult::__equiv); +inline constexpr weak_equality weak_equality::nonequivalent(_EqResult::__nonequiv); +inline constexpr bool operator==(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == _EqResult::__zero; +} +inline constexpr bool operator==(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v.__value_ == _EqResult::__zero; +} +inline constexpr bool operator!=(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != _EqResult::__zero; +} +inline constexpr bool operator!=(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v.__value_ != _EqResult::__zero; +} + +inline constexpr weak_equality operator<=>(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +inline constexpr weak_equality operator<=>(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v; +} + +class strong_equality { + explicit constexpr strong_equality(_EqResult __val) noexcept : __value_(__val) {} + +public: + static const strong_equality equal; + static const strong_equality nonequal; + static const strong_equality equivalent; + static const strong_equality nonequivalent; + + // conversion + constexpr operator weak_equality() const noexcept { + return __value_ == _EqResult::__zero ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + + // comparisons + friend constexpr bool operator==(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, strong_equality __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, strong_equality __v) noexcept; + + friend constexpr strong_equality operator<=>(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr strong_equality operator<=>(_CmpUnspecifiedParam, strong_equality __v) noexcept; + + // test helper + constexpr bool test_eq(strong_equality const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _EqResult __value_; +}; + +inline constexpr strong_equality strong_equality::equal(_EqResult::__equal); +inline constexpr strong_equality strong_equality::nonequal(_EqResult::__nonequal); +inline constexpr strong_equality strong_equality::equivalent(_EqResult::__equiv); +inline constexpr strong_equality strong_equality::nonequivalent(_EqResult::__nonequiv); +constexpr bool operator==(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == _EqResult::__zero; +} +constexpr bool operator==(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v.__value_ == _EqResult::__zero; +} +constexpr bool operator!=(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != _EqResult::__zero; +} +constexpr bool operator!=(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v.__value_ != _EqResult::__zero; +} + +constexpr strong_equality operator<=>(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr strong_equality operator<=>(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v; +} + +class partial_ordering { + using _ValueT = signed char; + explicit constexpr partial_ordering(_EqResult __v) noexcept + : __value_(_ValueT(__v)) {} + explicit constexpr partial_ordering(_OrdResult __v) noexcept + : __value_(_ValueT(__v)) {} + explicit constexpr partial_ordering(_NCmpResult __v) noexcept + : __value_(_ValueT(__v)) {} + + constexpr bool __is_ordered() const noexcept { + return __value_ != _ValueT(_NCmpResult::__unordered); + } + +public: + // valid values + static const partial_ordering less; + static const partial_ordering equivalent; + static const partial_ordering greater; + static const partial_ordering unordered; + + // conversion + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent : weak_equality::nonequivalent; + } + + // comparisons + friend constexpr bool operator==(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + + friend constexpr partial_ordering operator<=>(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr partial_ordering operator<=>(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(partial_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr partial_ordering partial_ordering::less(_OrdResult::__less); +inline constexpr partial_ordering partial_ordering::equivalent(_EqResult::__equiv); +inline constexpr partial_ordering partial_ordering::greater(_OrdResult::__greater); +inline constexpr partial_ordering partial_ordering::unordered(_NCmpResult ::__unordered); +constexpr bool operator==(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ == 0; +} +constexpr bool operator<(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ < 0; +} +constexpr bool operator<=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ <= 0; +} +constexpr bool operator>(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ > 0; +} +constexpr bool operator>=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 == __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 >= __v.__value_; +} +constexpr bool operator!=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return !__v.__is_ordered() || __v.__value_ != 0; +} +constexpr bool operator!=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return !__v.__is_ordered() || __v.__value_ != 0; +} + +constexpr partial_ordering operator<=>(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr partial_ordering operator<=>(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v < 0 ? partial_ordering::greater : (__v > 0 ? partial_ordering::less : __v); +} + +class weak_ordering { + using _ValueT = signed char; + explicit constexpr weak_ordering(_EqResult __v) noexcept : __value_(_ValueT(__v)) {} + explicit constexpr weak_ordering(_OrdResult __v) noexcept : __value_(_ValueT(__v)) {} + +public: + static const weak_ordering less; + static const weak_ordering equivalent; + static const weak_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + constexpr operator partial_ordering() const noexcept { + return __value_ == 0 ? partial_ordering::equivalent + : (__value_ < 0 ? partial_ordering::less : partial_ordering::greater); + } + + // comparisons + friend constexpr bool operator==(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + + friend constexpr weak_ordering operator<=>(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr weak_ordering operator<=>(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(weak_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr weak_ordering weak_ordering::less(_OrdResult::__less); +inline constexpr weak_ordering weak_ordering::equivalent(_EqResult::__equiv); +inline constexpr weak_ordering weak_ordering::greater(_OrdResult::__greater); +constexpr bool operator==(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == 0; +} +constexpr bool operator!=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != 0; +} +constexpr bool operator<(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ < 0; +} +constexpr bool operator<=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ <= 0; +} +constexpr bool operator>(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ > 0; +} +constexpr bool operator>=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 == __v.__value_; +} +constexpr bool operator!=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 != __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 >= __v.__value_; +} + +constexpr weak_ordering operator<=>(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr weak_ordering operator<=>(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return __v < 0 ? weak_ordering::greater : (__v > 0 ? weak_ordering::less : __v); +} + +class strong_ordering { + using _ValueT = signed char; + explicit constexpr strong_ordering(_EqResult __v) noexcept : __value_(static_cast(__v)) {} + explicit constexpr strong_ordering(_OrdResult __v) noexcept : __value_(static_cast(__v)) {} + +public: + static const strong_ordering less; + static const strong_ordering equal; + static const strong_ordering equivalent; + static const strong_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + constexpr operator strong_equality() const noexcept { + return __value_ == 0 ? strong_equality::equal + : strong_equality::nonequal; + } + constexpr operator partial_ordering() const noexcept { + return __value_ == 0 ? partial_ordering::equivalent + : (__value_ < 0 ? partial_ordering::less : partial_ordering::greater); + } + constexpr operator weak_ordering() const noexcept { + return __value_ == 0 ? weak_ordering::equivalent + : (__value_ < 0 ? weak_ordering::less : weak_ordering::greater); + } + + // comparisons + friend constexpr bool operator==(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + + friend constexpr strong_ordering operator<=>(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr strong_ordering operator<=>(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(strong_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr strong_ordering strong_ordering::less(_OrdResult::__less); +inline constexpr strong_ordering strong_ordering::equal(_EqResult::__equal); +inline constexpr strong_ordering strong_ordering::equivalent(_EqResult::__equiv); +inline constexpr strong_ordering strong_ordering::greater(_OrdResult::__greater); + +constexpr bool operator==(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == 0; +} +constexpr bool operator!=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != 0; +} +constexpr bool operator<(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ < 0; +} +constexpr bool operator<=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ <= 0; +} +constexpr bool operator>(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ > 0; +} +constexpr bool operator>=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 == __v.__value_; +} +constexpr bool operator!=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 != __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 >= __v.__value_; +} + +constexpr strong_ordering operator<=>(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr strong_ordering operator<=>(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return __v < 0 ? strong_ordering::greater : (__v > 0 ? strong_ordering::less : __v); +} + +// named comparison functions +constexpr bool is_eq(weak_equality __cmp) noexcept { return __cmp == 0; } +constexpr bool is_neq(weak_equality __cmp) noexcept { return __cmp != 0; } +constexpr bool is_lt(partial_ordering __cmp) noexcept { return __cmp < 0; } +constexpr bool is_lteq(partial_ordering __cmp) noexcept { return __cmp <= 0; } +constexpr bool is_gt(partial_ordering __cmp) noexcept { return __cmp > 0; } +constexpr bool is_gteq(partial_ordering __cmp) noexcept { return __cmp >= 0; } + +} // namespace __1 +} // end namespace std + +#endif // STD_COMPARE_H Index: test/PCH/cxx2a-compare.cpp =================================================================== --- /dev/null +++ test/PCH/cxx2a-compare.cpp @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -pedantic-errors -std=c++2a -emit-pch %s -o %t +// RUN: %clang_cc1 -pedantic-errors -std=c++2a -include-pch %t -verify %s +// RUN: %clang_cc1 -pedantic-errors -std=c++2a -include-pch %t -emit-llvm %s -o - + + +#ifndef HEADER +#define HEADER + +#include "Inputs/std-compare.h" +constexpr auto foo() { + return (42 <=> 101); +} + +inline auto bar(int x) { + return (1 <=> x); +} + +#else + +// expected-no-diagnostics + +static_assert(foo() < 0); + +auto bar2(int x) { + return bar(x); +} + +#endif Index: test/SemaCXX/Inputs/std-compare.h =================================================================== --- /dev/null +++ test/SemaCXX/Inputs/std-compare.h @@ -0,0 +1,437 @@ +#ifndef STD_COMPARE_H +#define STD_COMPARE_H + +namespace std { +inline namespace __1 { + +// exposition only +enum class _EqResult : unsigned char { + __zero = 0, + __equal = __zero, + __equiv = __equal, + __nonequal = 1, + __nonequiv = __nonequal +}; + +enum class _OrdResult : signed char { + __less = -1, + __greater = 1 +}; + +enum class _NCmpResult : signed char { + __unordered = -127 +}; + +struct _CmpUnspecifiedType; +using _CmpUnspecifiedParam = void (_CmpUnspecifiedType::*)(); + +class weak_equality { + constexpr explicit weak_equality(_EqResult __val) noexcept : __value_(__val) {} + +public: + static const weak_equality equivalent; + static const weak_equality nonequivalent; + + friend constexpr bool operator==(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, weak_equality __v) noexcept; + friend constexpr bool operator!=(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, weak_equality __v) noexcept; + friend constexpr weak_equality operator<=>(weak_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr weak_equality operator<=>(_CmpUnspecifiedParam, weak_equality __v) noexcept; + + // test helper + constexpr bool test_eq(weak_equality const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _EqResult __value_; +}; + +inline constexpr weak_equality weak_equality::equivalent(_EqResult::__equiv); +inline constexpr weak_equality weak_equality::nonequivalent(_EqResult::__nonequiv); +inline constexpr bool operator==(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == _EqResult::__zero; +} +inline constexpr bool operator==(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v.__value_ == _EqResult::__zero; +} +inline constexpr bool operator!=(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != _EqResult::__zero; +} +inline constexpr bool operator!=(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v.__value_ != _EqResult::__zero; +} + +inline constexpr weak_equality operator<=>(weak_equality __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +inline constexpr weak_equality operator<=>(_CmpUnspecifiedParam, weak_equality __v) noexcept { + return __v; +} + +class strong_equality { + explicit constexpr strong_equality(_EqResult __val) noexcept : __value_(__val) {} + +public: + static const strong_equality equal; + static const strong_equality nonequal; + static const strong_equality equivalent; + static const strong_equality nonequivalent; + + // conversion + constexpr operator weak_equality() const noexcept { + return __value_ == _EqResult::__zero ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + + // comparisons + friend constexpr bool operator==(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, strong_equality __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, strong_equality __v) noexcept; + + friend constexpr strong_equality operator<=>(strong_equality __v, _CmpUnspecifiedParam) noexcept; + friend constexpr strong_equality operator<=>(_CmpUnspecifiedParam, strong_equality __v) noexcept; + + // test helper + constexpr bool test_eq(strong_equality const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _EqResult __value_; +}; + +inline constexpr strong_equality strong_equality::equal(_EqResult::__equal); +inline constexpr strong_equality strong_equality::nonequal(_EqResult::__nonequal); +inline constexpr strong_equality strong_equality::equivalent(_EqResult::__equiv); +inline constexpr strong_equality strong_equality::nonequivalent(_EqResult::__nonequiv); +constexpr bool operator==(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == _EqResult::__zero; +} +constexpr bool operator==(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v.__value_ == _EqResult::__zero; +} +constexpr bool operator!=(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != _EqResult::__zero; +} +constexpr bool operator!=(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v.__value_ != _EqResult::__zero; +} + +constexpr strong_equality operator<=>(strong_equality __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr strong_equality operator<=>(_CmpUnspecifiedParam, strong_equality __v) noexcept { + return __v; +} + +class partial_ordering { + using _ValueT = signed char; + explicit constexpr partial_ordering(_EqResult __v) noexcept + : __value_(_ValueT(__v)) {} + explicit constexpr partial_ordering(_OrdResult __v) noexcept + : __value_(_ValueT(__v)) {} + explicit constexpr partial_ordering(_NCmpResult __v) noexcept + : __value_(_ValueT(__v)) {} + + constexpr bool __is_ordered() const noexcept { + return __value_ != _ValueT(_NCmpResult::__unordered); + } + +public: + // valid values + static const partial_ordering less; + static const partial_ordering equivalent; + static const partial_ordering greater; + static const partial_ordering unordered; + + // conversion + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent : weak_equality::nonequivalent; + } + + // comparisons + friend constexpr bool operator==(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + + friend constexpr partial_ordering operator<=>(partial_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr partial_ordering operator<=>(_CmpUnspecifiedParam, partial_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(partial_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr partial_ordering partial_ordering::less(_OrdResult::__less); +inline constexpr partial_ordering partial_ordering::equivalent(_EqResult::__equiv); +inline constexpr partial_ordering partial_ordering::greater(_OrdResult::__greater); +inline constexpr partial_ordering partial_ordering::unordered(_NCmpResult ::__unordered); +constexpr bool operator==(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ == 0; +} +constexpr bool operator<(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ < 0; +} +constexpr bool operator<=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ <= 0; +} +constexpr bool operator>(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ > 0; +} +constexpr bool operator>=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__is_ordered() && __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 == __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v.__is_ordered() && 0 >= __v.__value_; +} +constexpr bool operator!=(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return !__v.__is_ordered() || __v.__value_ != 0; +} +constexpr bool operator!=(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return !__v.__is_ordered() || __v.__value_ != 0; +} + +constexpr partial_ordering operator<=>(partial_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr partial_ordering operator<=>(_CmpUnspecifiedParam, partial_ordering __v) noexcept { + return __v < 0 ? partial_ordering::greater : (__v > 0 ? partial_ordering::less : __v); +} + +class weak_ordering { + using _ValueT = signed char; + explicit constexpr weak_ordering(_EqResult __v) noexcept : __value_(_ValueT(__v)) {} + explicit constexpr weak_ordering(_OrdResult __v) noexcept : __value_(_ValueT(__v)) {} + +public: + static const weak_ordering less; + static const weak_ordering equivalent; + static const weak_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + constexpr operator partial_ordering() const noexcept { + return __value_ == 0 ? partial_ordering::equivalent + : (__value_ < 0 ? partial_ordering::less : partial_ordering::greater); + } + + // comparisons + friend constexpr bool operator==(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + + friend constexpr weak_ordering operator<=>(weak_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr weak_ordering operator<=>(_CmpUnspecifiedParam, weak_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(weak_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr weak_ordering weak_ordering::less(_OrdResult::__less); +inline constexpr weak_ordering weak_ordering::equivalent(_EqResult::__equiv); +inline constexpr weak_ordering weak_ordering::greater(_OrdResult::__greater); +constexpr bool operator==(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == 0; +} +constexpr bool operator!=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != 0; +} +constexpr bool operator<(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ < 0; +} +constexpr bool operator<=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ <= 0; +} +constexpr bool operator>(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ > 0; +} +constexpr bool operator>=(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 == __v.__value_; +} +constexpr bool operator!=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 != __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return 0 >= __v.__value_; +} + +constexpr weak_ordering operator<=>(weak_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr weak_ordering operator<=>(_CmpUnspecifiedParam, weak_ordering __v) noexcept { + return __v < 0 ? weak_ordering::greater : (__v > 0 ? weak_ordering::less : __v); +} + +class strong_ordering { + using _ValueT = signed char; + explicit constexpr strong_ordering(_EqResult __v) noexcept : __value_(static_cast(__v)) {} + explicit constexpr strong_ordering(_OrdResult __v) noexcept : __value_(static_cast(__v)) {} + +public: + static const strong_ordering less; + static const strong_ordering equal; + static const strong_ordering equivalent; + static const strong_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept { + return __value_ == 0 ? weak_equality::equivalent + : weak_equality::nonequivalent; + } + constexpr operator strong_equality() const noexcept { + return __value_ == 0 ? strong_equality::equal + : strong_equality::nonequal; + } + constexpr operator partial_ordering() const noexcept { + return __value_ == 0 ? partial_ordering::equivalent + : (__value_ < 0 ? partial_ordering::less : partial_ordering::greater); + } + constexpr operator weak_ordering() const noexcept { + return __value_ == 0 ? weak_ordering::equivalent + : (__value_ < 0 ? weak_ordering::less : weak_ordering::greater); + } + + // comparisons + friend constexpr bool operator==(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator!=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator<=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator>=(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr bool operator==(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator!=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator<(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator<=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator>(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + friend constexpr bool operator>=(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + + friend constexpr strong_ordering operator<=>(strong_ordering __v, _CmpUnspecifiedParam) noexcept; + friend constexpr strong_ordering operator<=>(_CmpUnspecifiedParam, strong_ordering __v) noexcept; + + // test helper + constexpr bool test_eq(strong_ordering const &other) const noexcept { + return __value_ == other.__value_; + } + +private: + _ValueT __value_; +}; + +inline constexpr strong_ordering strong_ordering::less(_OrdResult::__less); +inline constexpr strong_ordering strong_ordering::equal(_EqResult::__equal); +inline constexpr strong_ordering strong_ordering::equivalent(_EqResult::__equiv); +inline constexpr strong_ordering strong_ordering::greater(_OrdResult::__greater); + +constexpr bool operator==(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ == 0; +} +constexpr bool operator!=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ != 0; +} +constexpr bool operator<(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ < 0; +} +constexpr bool operator<=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ <= 0; +} +constexpr bool operator>(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ > 0; +} +constexpr bool operator>=(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v.__value_ >= 0; +} +constexpr bool operator==(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 == __v.__value_; +} +constexpr bool operator!=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 != __v.__value_; +} +constexpr bool operator<(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 < __v.__value_; +} +constexpr bool operator<=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 <= __v.__value_; +} +constexpr bool operator>(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 > __v.__value_; +} +constexpr bool operator>=(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return 0 >= __v.__value_; +} + +constexpr strong_ordering operator<=>(strong_ordering __v, _CmpUnspecifiedParam) noexcept { + return __v; +} +constexpr strong_ordering operator<=>(_CmpUnspecifiedParam, strong_ordering __v) noexcept { + return __v < 0 ? strong_ordering::greater : (__v > 0 ? strong_ordering::less : __v); +} + +// named comparison functions +constexpr bool is_eq(weak_equality __cmp) noexcept { return __cmp == 0; } +constexpr bool is_neq(weak_equality __cmp) noexcept { return __cmp != 0; } +constexpr bool is_lt(partial_ordering __cmp) noexcept { return __cmp < 0; } +constexpr bool is_lteq(partial_ordering __cmp) noexcept { return __cmp <= 0; } +constexpr bool is_gt(partial_ordering __cmp) noexcept { return __cmp > 0; } +constexpr bool is_gteq(partial_ordering __cmp) noexcept { return __cmp >= 0; } + +} // namespace __1 +} // end namespace std + +#endif // STD_COMPARE_H Index: test/SemaCXX/compare-cxx2a.cpp =================================================================== --- test/SemaCXX/compare-cxx2a.cpp +++ test/SemaCXX/compare-cxx2a.cpp @@ -1,38 +1,51 @@ // Force x86-64 because some of our heuristics are actually based // on integer sizes. -// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -pedantic -verify -Wsign-compare -std=c++2a %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fcxx-exceptions -fsyntax-only -pedantic -verify -Wsign-compare -std=c++2a %s + +namespace BeforeStdTest { +struct T { + // expected-error@+1 {{cannot deduce return type of 'operator<=>' because type strong_ordering was not found; include }} + auto operator<=>(T const &) const = default; +}; + +} // namespace BeforeStdTest + +#include "Inputs/std-compare.h" + +#define ASSERT_TYPE(...) static_assert(__is_same(__VA_ARGS__)) +#define ASSERT_EXPR_TYPE(Expr, Expect) static_assert(__is_same(decltype(Expr), Expect)); void self_compare() { int a; - int b[3], c[3]; + int *b = nullptr; + (void)(a <=> a); // expected-warning {{self-comparison always evaluates to 'std::strong_ordering::equal'}} (void)(b <=> b); // expected-warning {{self-comparison always evaluates to 'std::strong_ordering::equal'}} - (void)(b <=> c); // expected-warning {{array comparison always evaluates to a constant}} } void test0(long a, unsigned long b) { - enum EnumA {A}; + enum EnumA : int {A}; enum EnumB {B}; enum EnumC {C = 0x10000}; - // (a,b) - // FIXME: <=> should never produce -Wsign-compare warnings. All the possible error - // cases involve narrowing conversions and so are ill-formed. - (void)(a <=> (unsigned long) b); // expected-warning {{comparison of integers of different signs}} + (void)((short)a <=> (unsigned short)b); + + // (a,b) + (void)(a <=> (unsigned long)b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} (void)(a <=> (unsigned int) b); (void)(a <=> (unsigned short) b); (void)(a <=> (unsigned char) b); - (void)((long) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((int) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((short) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((signed char) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((long) a <=> (unsigned long) b); // expected-warning {{comparison of integers of different signs}} - (void)((int) a <=> (unsigned int) b); // expected-warning {{comparison of integers of different signs}} + (void)((long)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((short)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((signed char)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((long)a <=> (unsigned long)b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)a <=> (unsigned int)b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} (void)((short) a <=> (unsigned short) b); (void)((signed char) a <=> (unsigned char) b); -#if 0 + (void)(A < 42); // (A,b) (void)(A <=> (unsigned long) b); (void)(A <=> (unsigned int) b); @@ -48,7 +61,7 @@ (void)((signed char) A <=> (unsigned char) b); // (a,B) - (void)(a <=> (unsigned long) B); + (void)(a <=> (unsigned long) B); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} (void)(a <=> (unsigned int) B); (void)(a <=> (unsigned short) B); (void)(a <=> (unsigned char) B); @@ -56,8 +69,8 @@ (void)((int) a <=> B); (void)((short) a <=> B); (void)((signed char) a <=> B); - (void)((long) a <=> (unsigned long) B); - (void)((int) a <=> (unsigned int) B); + (void)((long) a <=> (unsigned long) B); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} + (void)((int) a <=> (unsigned int) B); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'}} (void)((short) a <=> (unsigned short) B); (void)((signed char) a <=> (unsigned char) B); @@ -76,7 +89,7 @@ (void)((signed char) C <=> (unsigned char) b); // (a,C) - (void)(a <=> (unsigned long) C); + (void)(a <=> (unsigned long) C); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} (void)(a <=> (unsigned int) C); (void)(a <=> (unsigned short) C); (void)(a <=> (unsigned char) C); @@ -84,11 +97,10 @@ (void)((int) a <=> C); (void)((short) a <=> C); // expected-warning {{comparison of constant 'C' (65536) with expression of type 'short' is always 'std::strong_ordering::less'}} (void)((signed char) a <=> C); // expected-warning {{comparison of constant 'C' (65536) with expression of type 'signed char' is always 'std::strong_ordering::less'}} - (void)((long) a <=> (unsigned long) C); - (void)((int) a <=> (unsigned int) C); + (void)((long) a <=> (unsigned long) C); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} + (void)((int) a <=> (unsigned int) C); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'}} (void)((short) a <=> (unsigned short) C); (void)((signed char) a <=> (unsigned char) C); -#endif // (0x80000,b) (void)(0x80000 <=> (unsigned long) b); @@ -105,7 +117,7 @@ (void)((signed char) 0x80000 <=> (unsigned char) b); // (a,0x80000) - (void)(a <=> (unsigned long) 0x80000); // expected-warning {{comparison of integers of different signs}} + (void)(a <=> (unsigned long)0x80000); // expected-error {{argument to 'operator<=>' cannot be narrowed}} (void)(a <=> (unsigned int) 0x80000); (void)(a <=> (unsigned short) 0x80000); (void)(a <=> (unsigned char) 0x80000); @@ -113,19 +125,23 @@ (void)((int) a <=> 0x80000); (void)((short) a <=> 0x80000); // expected-warning {{comparison of constant 524288 with expression of type 'short' is always 'std::strong_ordering::less'}} (void)((signed char) a <=> 0x80000); // expected-warning {{comparison of constant 524288 with expression of type 'signed char' is always 'std::strong_ordering::less'}} - (void)((long) a <=> (unsigned long) 0x80000); // expected-warning {{comparison of integers of different signs}} - (void)((int) a <=> (unsigned int) 0x80000); // expected-warning {{comparison of integers of different signs}} + (void)((long)a <=> (unsigned long)0x80000); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)a <=> (unsigned int)0x80000); // expected-error {{argument to 'operator<=>' cannot be narrowed}} (void)((short) a <=> (unsigned short) 0x80000); (void)((signed char) a <=> (unsigned char) 0x80000); } -void test5(bool b) { - (void) (b <=> -10); // expected-warning{{comparison of constant -10 with expression of type 'bool' is always 'std::strong_ordering::greater'}} - (void) (b <=> -1); // expected-warning{{comparison of constant -1 with expression of type 'bool' is always 'std::strong_ordering::greater'}} - (void) (b <=> 0); - (void) (b <=> 1); - (void) (b <=> 2); // expected-warning{{comparison of constant 2 with expression of type 'bool' is always 'std::strong_ordering::less'}} - (void) (b <=> 10); // expected-warning{{comparison of constant 10 with expression of type 'bool' is always 'std::strong_ordering::less'}} +void test5(bool b, bool b2) { + enum EnumA { A }; + (void)(b <=> b2); // OK + (void)(true <=> b); // OK + (void)(b <=> -10); // expected-error {{invalid operands to binary expression ('bool' and 'int')}} + (void)(b <=> char(1)); // expected-error {{invalid operands to binary expression ('bool' and 'char')}} + (void)(b <=> A); // expected-error {{invalid operands to binary expression ('bool' and 'EnumA')}} + + // FIXME: Should this be accepted when narrowing doesn't occur? + (void)(b <=> 0); // expected-error {{invalid operands to binary expression ('bool' and 'int')}} + (void)(b <=> 1); // expected-error {{invalid operands to binary expression ('bool' and 'int')}} } void test6(signed char sc) { @@ -142,13 +158,13 @@ (void)((unsigned long)other <=> (unsigned)(0xffff'ffff)); // Common unsigned, other signed, constant unsigned - (void)((int)other <=> (unsigned long)(0xffff'ffff'ffff'ffff)); // expected-warning{{different signs}} - (void)((int)other <=> (unsigned long)(0x0000'0000'ffff'ffff)); // expected-warning{{different signs}} - (void)((int)other <=> (unsigned long)(0x0000'0000'0fff'ffff)); // expected-warning{{different signs}} - (void)((int)other <=> (unsigned)(0x8000'0000)); // expected-warning{{different signs}} + (void)((int)other <=> (unsigned long)(0xffff'ffff'ffff'ffff)); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)other <=> (unsigned long)(0x0000'0000'ffff'ffff)); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)other <=> (unsigned long)(0x0000'0000'0fff'ffff)); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)other <=> (unsigned)(0x8000'0000)); // expected-error {{argument to 'operator<=>' cannot be narrowed}} // Common unsigned, other unsigned, constant signed - (void)((unsigned long)other <=> (int)(0xffff'ffff)); // expected-warning{{different signs}} + (void)((unsigned long)other <=> (int)(0xffff'ffff)); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned long'}} // Common unsigned, other signed, constant signed // Should not be possible as the common type should also be signed. @@ -172,3 +188,653 @@ (void)((unsigned char)other <=> (unsigned short)(0x100)); // expected-warning{{less}} (void)((unsigned short)other <=> (unsigned char)(0xff)); } + +void test8(void *vp, const void *cvp, int *ip) { + (void)(vp <=> cvp); // OK, void* comparisons are allowed. + (void)(vp <=> ip); + (void)(ip <=> cvp); +} + +void test9(long double ld, double d, float f, int i, long long ll) { + (void)(f <=> ll); // OK, floating-point to integer is OK + (void)(d <=> ld); + (void)(i <=> f); +} + +typedef int *INTPTR; +void test_typedef_bug(int *x, INTPTR y) { + (void)(x <=> y); +} + +using nullptr_t = decltype(nullptr); + +struct Class {}; +struct ClassB : Class {}; +struct Class2 {}; +using FnTy = void(int); +using FnTy2 = long(int); +using MemFnTy = void (Class::*)() const; +using MemFnTyB = void (ClassB::*)() const; +using MemFnTy2 = void (Class::*)(); +using MemFnTy3 = void (Class2::*)() const; +using MemDataTy = long(Class::*); + +void test_nullptr(int *x, FnTy *fp, MemFnTy memp, MemDataTy memdp) { + auto r1 = (nullptr <=> nullptr); + ASSERT_EXPR_TYPE(r1, std::strong_equality); + + auto r2 = (nullptr <=> x); + ASSERT_EXPR_TYPE(r2, std::strong_equality); + + auto r3 = (fp <=> nullptr); + ASSERT_EXPR_TYPE(r3, std::strong_equality); + + auto r4 = (0 <=> fp); + ASSERT_EXPR_TYPE(r4, std::strong_equality); + + auto r5 = (nullptr <=> memp); + ASSERT_EXPR_TYPE(r5, std::strong_equality); + + auto r6 = (0 <=> memdp); + ASSERT_EXPR_TYPE(r6, std::strong_equality); + + auto r7 = (0 <=> nullptr); + ASSERT_EXPR_TYPE(r7, std::strong_equality); +} + +void test_compatible_pointer(FnTy *f1, FnTy2 *f2, MemFnTy mf1, MemFnTyB mfb, + MemFnTy2 mf2, MemFnTy3 mf3) { + (void)(f1 <=> f2); // expected-error {{distinct pointer types}} + + auto r1 = (mf1 <=> mfb); // OK + ASSERT_EXPR_TYPE(r1, std::strong_equality); + ASSERT_EXPR_TYPE((mf1 <=> mfb), std::strong_equality); + + (void)(mf1 <=> mf2); // expected-error {{distinct pointer types}} + (void)(mf3 <=> mf1); // expected-error {{distinct pointer types}} +} + +// Test that variable narrowing is deferred for value dependent expressions +template +auto test_template_overflow() { + // expected-error@+1 {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned long'}} + return (Val <=> (unsigned long)0); +} +template auto test_template_overflow<0>(); +template auto test_template_overflow<-1>(); // expected-note {{requested here}} + +void test_enum_integral_compare() { + enum EnumA : int {A, ANeg = -1, AMax = __INT_MAX__}; + enum EnumB : unsigned {B, BMax = __UINT32_MAX__ }; + enum EnumC : int {C = -1, C0 = 0}; + + (void)(A <=> C); // expected-error {{invalid operands to binary expression ('EnumA' and 'EnumC')}} + + (void)(A <=> (unsigned)0); + (void)((unsigned)0 <=> A); + (void)(ANeg <=> (unsigned)0); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)((unsigned)0 <=> ANeg); // expected-error {{cannot be narrowed}} + + (void)(B <=> 42); + (void)(42 <=> B); + (void)(B <=> (unsigned long long)42); + (void)(B <=> -1); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)(BMax <=> (unsigned long)-1); + + (void)(C0 <=> (unsigned)42); + (void)(C <=> (unsigned)42); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} +} + +namespace EnumCompareTests { + +enum class EnumA { A, A2 }; +enum class EnumB { B }; +enum class EnumC : unsigned { C }; + +void test_enum_enum_compare_no_builtin() { + auto r1 = (EnumA::A <=> EnumA::A2); // OK + ASSERT_EXPR_TYPE(r1, std::strong_ordering); + (void)(EnumA::A <=> EnumA::A); // expected-warning {{self-comparison always evaluates to 'std::strong_ordering::equal'}} + (void)(EnumA::A <=> EnumB::B); // expected-error {{invalid operands to binary expression ('EnumCompareTests::EnumA' and 'EnumCompareTests::EnumB')}} + (void)(EnumB::B <=> EnumA::A); // expected-error {{invalid operands}} +} + +template +struct Tag {}; +std::strong_ordering operator<=>(EnumA, EnumA) { + return std::strong_ordering::equal; +} +// expected-note@+1 {{candidate function}}, +std::strong_ordering operator<=>(EnumA a, EnumB b) { + return ((int)a <=> (int)b); +} + +void test_enum_ovl_provided() { + auto r1 = (EnumA::A <=> EnumA::A); + ASSERT_EXPR_TYPE(r1, std::strong_ordering); + auto r2 = (EnumA::A <=> EnumB::B); + ASSERT_EXPR_TYPE(r2, std::strong_ordering); + (void)(EnumB::B <=> EnumA::A); // OK, chooses reverse order synthesized candidate. + (void)(EnumB::B <=> EnumC::C); // expected-error {{invalid operands to binary expression ('EnumCompareTests::EnumB' and 'EnumCompareTests::EnumC')}} +} + +void enum_float_test() { + enum EnumA { A }; + (void)(A <=> (float)0); // expected-error {{invalid operands to binary expression ('EnumA' and 'float')}} + (void)((double)0 <=> A); // expected-error {{invalid operands to binary expression ('double' and 'EnumA')}} + (void)((long double)0 <=> A); // expected-error {{invalid operands to binary expression ('long double' and 'EnumA')}} +} + +} // namespace EnumCompareTests + +namespace TestUserDefinedConvSeq { + +template +struct Conv { + constexpr operator T() const { return Val; } + operator T() { return Val; } +}; + +void test_user_conv() { + { + using C = Conv; + C c; + const C cc; + (void)(0 <=> c); + (void)(c <=> -1); + (void)((unsigned)0 <=> cc); + (void)((unsigned)0 <=> c); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'}} + } + { + using C = Conv; + C c; + const C cc; + (void)(c <=> 0); + (void)(cc <=> (unsigned)0); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)(c <=> (unsigned)0); // expected-error {{cannot be narrowed from type 'int' to 'unsigned int'}} + } +} + +} // namespace TestUserDefinedConvSeq + +void test_array_conv() { + int arr[5]; + int *ap = arr + 2; + int arr2[3]; + (void)(arr <=> arr); // expected-error {{invalid operands to binary expression ('int [5]' and 'int [5]')}} + (void)(+arr <=> arr); +} + +void test_mixed_float_int(float f, double d, long double ld) { + extern int i; + extern unsigned u; + extern long l; + extern short s; + extern unsigned short us; + auto r1 = (f <=> i); + ASSERT_EXPR_TYPE(r1, std::partial_ordering); + + auto r2 = (us <=> ld); + ASSERT_EXPR_TYPE(r2, std::partial_ordering); + + auto r3 = (s <=> f); + ASSERT_EXPR_TYPE(r3, std::partial_ordering); + + auto r4 = (0.0 <=> i); + ASSERT_EXPR_TYPE(r4, std::partial_ordering); + +} + +namespace TestRewritting { + +struct T { + int x; + // expected-note@+1 {{candidate}} + constexpr std::strong_ordering operator<=>(T y) const { + return (x <=> y.x); + } +}; + +struct U { + int x; + // FIXME: This diagnostic is wrong-ish. + // expected-note@+1 {{candidate function not viable: requires single argument 'y', but 2 arguments were provided}} + constexpr std::strong_equality operator<=>(T y) const { + if (x == y.x) + return std::strong_equality::equal; + return std::strong_equality::nonequal; + } +}; + +struct X { int x; }; +struct Y { int x; }; +template +struct Tag {}; +// expected-note@+1 2 {{candidate}} +Tag<0> operator<=>(X, Y) { + return {}; +} +// expected-note@+1 2 {{candidate}} +constexpr auto operator<=>(Y y, X x) { + return y.x <=> x.x; +} + +void foo() { + T t{42}; + T t2{0}; + U u{101}; + auto r1 = (t <=> u); + ASSERT_EXPR_TYPE(r1, std::strong_equality); + auto r2 = (t <=> t2); + ASSERT_EXPR_TYPE(r2, std::strong_ordering); + + auto r3 = t == u; + ASSERT_EXPR_TYPE(r3, bool); + + (void)(t < u); // expected-error {{invalid operands to binary expression ('TestRewritting::T' and 'TestRewritting::U')}} + + constexpr X x{1}; + constexpr Y y{2}; + constexpr auto r4 = (y < x); + static_assert(r4 == false); + constexpr auto r5 = (x < y); + static_assert(r5 == true); +} + +} // namespace TestRewritting + +// The implicit object parameter is not considered when performing partial +// ordering. That makes the reverse synthesized candidates ambiguous with the +// rewritten candidates if any of them resolve to a member function. +namespace TestOvlMatchingIgnoresImplicitObject { +struct U; +// expected-note@+2 {{candidate}} +struct T { + std::strong_ordering operator<=>(U const &RHS) const; +}; +// expected-note@+2 {{candidate}} +struct U { + std::strong_ordering operator<=>(T const &RHS) const; +}; + +void test() { + // expected-error@+1 {{use of overloaded operator '<' is ambiguous}} + (void)(T{} < U{}); +} + +} // namespace TestOvlMatchingIgnoresImplicitObject + +namespace IllFormedSpecialMemberTest { + +struct T1 { + // expected-error@+1 {{the parameter for this explicitly-defaulted comparison operator must have type 'const IllFormedSpecialMemberTest::T1 &', but parameter has type 'IllFormedSpecialMemberTest::T1'}} + void operator<=>(T1) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + void operator<=>(T1 &) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + void operator<=>(int) const = default; + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 1 parameter)}} + void operator<=>() const = default; + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 3 parameters)}} + void operator<=>(T1 const &, T1 const &) = default; + + // expected-error@+2 {{an explicitly-defaulted comparison operator cannot be a template}} + template + void operator<=>(U const &) = default; + + // FIXME(EricWF): This should delete itself. + void operator<=>(T1 const &) const = default; // OK +}; +// expected-error@+1 {{definition of explicitly defaulted comparison operator}} +void T1::operator<=>(T1 const &) const = default; + +struct T2 { + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 1 parameter)}} + friend void operator<=>(T2 const &) = default; + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 3 parameters)}} + friend void operator<=>(T2 const &, T2 const &, T2 const &) = default; + + // FIXME(EricWF): Improve this diagnostic to point to the parameter. + // expected-error@+2 {{explicitly-defaulted comparison operator must have type}} + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + friend void operator<=>(T2, T2) = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + friend void operator<=>(T2 &, T2 const &) = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + friend void operator<=>(T2 const &, T2 const volatile &) = default; + + friend auto operator<=>(T2 const &, T2 const &) = default; //OK + + // expected-error@+2 {{an explicitly-defaulted comparison operator cannot be a template}} + template + friend void operator<=>(Tp const &, Tp const &) = default; +}; + +struct T3; +// expected-note@+1 {{previous declaration is here}} +auto operator<=>(T3 const &, T3 const &); +struct T3 { + // expected-error@+1 {{defaulted definition must be first declaration}} + friend auto operator<=>(T3 const &, T3 const &) = default; +}; + +struct T4 { + bool operator<(T4 const &) const = default; // OK + bool operator>(T4 const &) const = default; + bool operator==(T4 const &) const = default; + bool operator!=(T4 const &) const = default; + + friend bool operator<(T4 const &, T4 const &) = default; + friend bool operator!=(T4 const &, T4 const &) = default; + + // expected-error@+1 {{explicitly-defaulted comparison operator must return 'bool'}} + int operator<=(T4 const &) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must return 'bool'}} + bool &operator>=(T4 const &) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must return 'bool'}} + friend void operator<=(T4 const &, T4 const &) = default; +}; + +struct T5 { + bool operator<(T5 const &) const = default; + friend auto operator<=>(T5 const &, T5 const &) = default; +}; + +// expected-error@+1 {{definition of explicitly defaulted comparison operator}} +bool T5::operator<(T5 const &) const { + return false; +} + +} // namespace IllFormedSpecialMemberTest + +namespace SpecialMemberTest { + +struct T { + int x; + auto operator<=>(T const &) const = default; +}; + +struct U { + int x; + friend auto operator<=>(U const &, U const &) = default; +}; + +} // namespace SpecialMemberTest + +namespace ConstexprDefaultTest { +struct T { + int x, y, z; + constexpr std::strong_ordering operator<=>(T const &other) const = default; +}; + +constexpr T t1{0, 0, 0}; +constexpr T t2{1, 1, 1}; +static_assert(t1 < t2); +static_assert(t1 != t2); + +// expected-error@+1 {{static_assert failed due to requirement 't1 != (const ConstexprDefaultTest::T &)t1'}} +static_assert(t1 != (T const &)t1); + +} // namespace ConstexprDefaultTest + +namespace TestCommonCategoryType { +using SO = std::strong_ordering; +using WO = std::weak_ordering; +using PO = std::partial_ordering; +using SE = std::strong_equality; +using WE = std::weak_equality; + +template +struct Tag { using type = T; }; +template +struct CmpResult { + friend Tp operator<=>(CmpResult const &, CmpResult const &); +}; +template +struct CmpResult> { + friend T operator<=>(CmpResult const &, CmpResult const &); +}; +template <> +struct CmpResult> { + friend auto operator<=>(CmpResult const &, CmpResult const &) = delete; +}; + +template +struct Cmp : CmpResult... { + friend auto operator<=>(Cmp const &, Cmp const &) = default; +}; + +template +struct Cmp2 : CmpResult... { + auto operator<=>(Cmp2 const &) const = default; +}; + +void test() { + { + using C = Cmp<>; + C c1, c2; + ASSERT_EXPR_TYPE(c1 <=> c2, SO); + } + { + using C = Cmp; + C c1, c2; + (void)(c1 <=> c2); + ASSERT_EXPR_TYPE(c1 <=> c2, SO); + } + { + using C = Cmp2; + C c1, c2; + (void)(c1 <=> c2); + ASSERT_EXPR_TYPE(c1 <=> c2, SO); + } +} + +} // namespace TestCommonCategoryType + +namespace DefaultTest { +struct T { + int x; + std::strong_ordering operator<=>(T const &) const { return std::strong_ordering::equal; } +}; +struct U { + float y; + friend std::partial_ordering operator<=>(U const &, U const &) = default; +}; +struct TU : public T, public U { + friend auto operator<=>(TU const &, TU const &) = default; +}; + +void test1(T x, T y) { + (void)(x <=> y); +} +} // namespace DefaultTest + +namespace TestRecursion { + +template +struct U { + Tp &t; + auto operator<=>(U const &) const = default; +}; + +struct T { + U u; + // FIXME(EricWF): Is this diagnostic helpful? + // expected-note@+1 {{explicitly defaulted function was implicitly deleted here}} + auto operator<=>(T const &) const = default; +}; + +void test(T &t1, T &t2) { + // expected-error@+1 {{object of type 'TestRecursion::T' cannot be compared because its comparison operator is implicitly deleted}} + using R = decltype(t1 <=> t2); +} + +} // namespace TestRecursion + +namespace AutoDeductionTest { +template +struct U { + Tp &t; + auto operator<=>(U const &u) const { + return t <=> u.t; + } +}; + +struct T { + U u; + auto operator<=>(T const &) const = default; +}; + +void test(T &t1, T &t2) { + using R = decltype(t1 <=> t2); + ASSERT_TYPE(R, std::partial_ordering); +} +} // namespace AutoDeductionTest + +namespace RecursionTest3 { +template +struct U { + bool b; + Tp &t; + float f; + auto operator<=>(U const &u) const { + if (b) + return t <=> u.t; + // expected-error@+1 {{'auto' in return type deduced as 'std::__1::partial_ordering' here but deduced as 'std::__1::strong_ordering' in earlier return statement}} + return f <=> u.f; + } +}; + +struct T { + U u; + // expected-note@+1 {{in instantiation of member function}} + auto operator<=>(T const &) const = default; +}; + +// FIXME(EricWF): Why isn't this working? +void test(T &t1, T &t2) { + using R = decltype(t1 <=> t2); +} +} // namespace RecursionTest3 + +namespace ConstexprDeclTest { +struct T { + // non-constexpr + std::strong_ordering operator<=>(T const &) const { return std::strong_ordering::equal; } +}; + +struct U { + T t; + // expected-error@+1 {{defaulted definition of comparison operator is not constexpr}} + constexpr auto operator<=>(U const &) const = default; +}; + +struct V { + T t; + // expected-note@+1 {{declared here}} + friend auto operator<=>(V const &, V const &) = default; +}; +// expected-error@+2 {{must be initialized by a constant expression}} +// expected-note@+1 {{non-constexpr function 'operator<=>' cannot be used in a constant expression}} +constexpr auto R1 = (V{} <=> V{}); + +} // namespace ConstexprDeclTest + +namespace AccessTest { +struct T { + T() : x(0), y(0), z(0) {} + T(int x, int y, int z) : x(x), y(y), z(z) {} + int x; + +protected: + int y; + +private: + int z; + +public: + auto operator<=>(T const &) const = default; +}; +struct U { + U() : x(0), y(0), z(0) {} + U(int x, int y, int z) : x(x), y(y), z(z) {} + int x; + +protected: + int y; + +private: + float z; + +public: + auto operator<=>(U const &) const = default; +}; + +void test() { + T t1, t2; + (void)(t1 <=> t2); + { + U u1, u2; + auto r = (u1 <=> u2); + ASSERT_EXPR_TYPE(r, std::partial_ordering); + } +} + +struct P { + float x; + +private: + auto operator<=>(P const &) const = default; +}; + +struct D : P { + // expected-note@+1 {{explicitly defaulted function was implicitly deleted here}} + auto operator<=>(D const &) const = default; +}; +void test2() { + D d1, d2; + // expected-error@+1 {{object of type 'AccessTest::D' cannot be compared because its comparison operator is implicitly deleted}} + auto r = (d1 <=> d2); +} + +} // namespace AccessTest + +namespace FieldTests { +struct X { + X() { x = 0, y = 0; } + int x : 2; + int y : 3; + auto operator<=>(X const &) const = default; +}; +void test() { + X x1, x2; + auto r = x1 <=> x2; +} + +struct Vol { + std::strong_ordering operator<=>(Vol const &) const volatile; +}; +struct Vol2 : Vol { + auto operator<=>(Vol2 const &) const = default; +}; +} // namespace FieldTests + +namespace CachedLookupTest { +struct T {}; + +struct U { + T t; + // expected-note@+1 {{implicitly deleted here}} + auto operator<=>(U const &) const = default; +}; +// expected-error@+1 {{comparison operator is implicitly deleted}} +auto r1 = U{} <=> U{}; + +std::strong_ordering operator<=>(T const &, T const &); + +struct V { + T t; + auto operator<=>(V const &) const = default; +}; +auto r2 = V{} <=> V{}; +} // namespace CachedLookupTest Index: test/SemaCXX/constant-expression-cxx2a.cpp =================================================================== --- test/SemaCXX/constant-expression-cxx2a.cpp +++ test/SemaCXX/constant-expression-cxx2a.cpp @@ -1,5 +1,7 @@ // RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu +#include "Inputs/std-compare.h" + namespace ThreeWayComparison { struct A { int n; @@ -11,17 +13,167 @@ static_assert(A{2} <=> A{1} > 0); static_assert(A{2} <=> A{2} == 0); - // Note: not yet supported. - static_assert(1 <=> 2 < 0); // expected-error {{invalid operands}} - static_assert(2 <=> 1 > 0); // expected-error {{invalid operands}} - static_assert(1 <=> 1 == 0); // expected-error {{invalid operands}} + static_assert(1 <=> 2 < 0); + static_assert(2 <=> 1 > 0); + static_assert(1 <=> 1 == 0); constexpr int k = (1 <=> 1, 0); - // expected-error@-1 {{constexpr variable 'k' must be initialized by a constant expression}} - // expected-warning@-2 {{three-way comparison result unused}} + // expected-warning@-1 {{three-way comparison result unused}} + + static_assert(std::strong_ordering::equal == 0); - constexpr void f() { // expected-error {{constant expression}} - void(1 <=> 1); // expected-note {{constant expression}} + constexpr void f() { + void(1 <=> 1); } - // TODO: defaulted operator <=> + struct MemPtr { + void foo() {} + void bar() {} + int data; + int data2; + long data3; + }; + + struct MemPtr2 { + void foo() {} + void bar() {} + int data; + int data2; + long data3; + }; + using MemPtrT = void (MemPtr::*)(); + + using FnPtrT = void (*)(); + + void FnPtr1() {} + void FnPtr2() {} + +#define CHECK(...) ((__VA_ARGS__) ? void() : throw "error") +#define CHECK_TYPE(...) static_assert(__is_same(__VA_ARGS__)); + +constexpr bool test_constexpr_success = [] { + { + auto &EQ = std::strong_ordering::equal; + auto &LESS = std::strong_ordering::less; + auto &GREATER = std::strong_ordering::greater; + using SO = std::strong_ordering; + auto eq = (42 <=> 42); + CHECK_TYPE(decltype(eq), SO); + CHECK(eq.test_eq(EQ)); + + auto less = (-1 <=> 0); + CHECK_TYPE(decltype(less), SO); + CHECK(less.test_eq(LESS)); + + auto greater = (42l <=> 1u); + CHECK_TYPE(decltype(greater), SO); + CHECK(greater.test_eq(GREATER)); + } + { + using PO = std::partial_ordering; + auto EQUIV = PO::equivalent; + auto LESS = PO::less; + auto GREATER = PO::greater; + + auto eq = (42.0 <=> 42.0); + CHECK_TYPE(decltype(eq), PO); + CHECK(eq.test_eq(EQUIV)); + + auto less = (39.0 <=> 42.0); + CHECK_TYPE(decltype(less), PO); + CHECK(less.test_eq(LESS)); + + auto greater = (-10.123 <=> -101.1); + CHECK_TYPE(decltype(greater), PO); + CHECK(greater.test_eq(GREATER)); + } + { + using SE = std::strong_equality; + auto EQ = SE::equal; + auto NEQ = SE::nonequal; + + MemPtrT P1 = &MemPtr::foo; + MemPtrT P12 = &MemPtr::foo; + MemPtrT P2 = &MemPtr::bar; + MemPtrT P3 = nullptr; + + auto eq = (P1 <=> P12); + CHECK_TYPE(decltype(eq), SE); + CHECK(eq.test_eq(EQ)); + + auto neq = (P1 <=> P2); + CHECK_TYPE(decltype(eq), SE); + CHECK(neq.test_eq(NEQ)); + + auto eq2 = (P3 <=> nullptr); + CHECK_TYPE(decltype(eq2), SE); + CHECK(eq2.test_eq(EQ)); + } + { + using SE = std::strong_equality; + auto EQ = SE::equal; + auto NEQ = SE::nonequal; + + FnPtrT F1 = &FnPtr1; + FnPtrT F12 = &FnPtr1; + FnPtrT F2 = &FnPtr2; + FnPtrT F3 = nullptr; + + auto eq = (F1 <=> F12); + CHECK_TYPE(decltype(eq), SE); + CHECK(eq.test_eq(EQ)); + + auto neq = (F1 <=> F2); + CHECK_TYPE(decltype(neq), SE); + CHECK(neq.test_eq(NEQ)); } + { // mixed nullptr tests + using SO = std::strong_ordering; + using SE = std::strong_equality; + + int x = 42; + int *xp = &x; + + MemPtrT mf = nullptr; + MemPtrT mf2 = &MemPtr::foo; + auto r3 = (mf <=> nullptr); + CHECK_TYPE(decltype(r3), std::strong_equality); + CHECK(r3.test_eq(SE::equal)); + } + + return true; +}(); + +template +constexpr bool test_constexpr() { + using nullptr_t = decltype(nullptr); + using LHSTy = decltype(LHS); + using RHSTy = decltype(RHS); + // expected-note@+1 {{subexpression not valid in a constant expression}} + auto Res = (LHS <=> RHS); + if constexpr (__is_same(LHSTy, nullptr_t) || __is_same(RHSTy, nullptr_t)) { + CHECK_TYPE(decltype(Res), std::strong_equality); + } + if (ExpectTrue) + return Res == 0; + return Res != 0; +} +int dummy = 42; +int dummy2 = 101; + +constexpr bool tc1 = test_constexpr(); +constexpr bool tc2 = test_constexpr<&dummy, nullptr>(); + +// OK, equality comparison only +constexpr bool tc3 = test_constexpr<&MemPtr::foo, nullptr>(); +constexpr bool tc4 = test_constexpr(); +constexpr bool tc5 = test_constexpr<&MemPtr::foo, &MemPtr::bar>(); + +constexpr bool tc6 = test_constexpr<&MemPtr::data, nullptr>(); +constexpr bool tc7 = test_constexpr(); +constexpr bool tc8 = test_constexpr<&MemPtr::data, &MemPtr::data2>(); + +// expected-error@+1 {{must be initialized by a constant expression}} +constexpr bool tc9 = test_constexpr<&dummy, &dummy2>(); // expected-note {{in call}} + +// TODO: defaulted operator <=> +} // namespace ThreeWayComparison Index: test/SemaCXX/std-compare-cxx2a.cpp =================================================================== --- /dev/null +++ test/SemaCXX/std-compare-cxx2a.cpp @@ -0,0 +1,44 @@ +// Test diagnostics for ill-formed STL headers. + +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fcxx-exceptions -fsyntax-only -pedantic -verify -Wsign-compare -std=c++2a %s + +void compare_not_found_test() { + // expected-error@+1 {{cannot deduce return type of 'operator<=>' because type partial_ordering was not found; include }} + (void)(0.0 <=> 42.123); +} + +namespace std { +inline namespace __1 { +struct partial_ordering; // expected-note {{forward declaration}} +} +} // namespace std + +auto compare_incomplete_test() { + // expected-error@+1 {{incomplete type 'std::partial_ordering' where a complete type is required}} + return (-1.2 <=> 123.0); +} + +namespace std { +inline namespace __1 { +struct partial_ordering { +}; +} // namespace __1 +} // namespace std + +auto missing_member_test() { + // expected-error@+1 {{'std::partial_ordering' is missing a member named 'equivalent'}} + return (1.0 <=> 1.0); +} + +namespace std { +inline namespace __1 { +struct strong_ordering { + static const strong_ordering equivalent; // expected-note {{declared here}} +}; +} // namespace __1 +} // namespace std + +auto test_non_constexpr_var() { + // expected-error@+1 {{standard library implementation of 'std::strong_ordering' is not supported; member 'equivalent' does not have expected form}} + return (1 <=> 0); +}