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" @@ -1973,6 +1974,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,201 @@ +//===- 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 +#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 values built from the + /// standard library. The key is a value of ComparisonCategoryResult. + mutable llvm::DenseMap Objects; + + ComparisonCategoryInfo(const ASTContext &Ctx) : Ctx(Ctx) {} + +public: + /// \brief Wether Sema has fully checked the type and result values for this + /// comparison category types before. + mutable bool IsFullyChecked = false; + +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 an expression referencing the member of the specified + /// comparison category. For example 'std::strong_equality::equal' + const VarDecl *getResultValue(ComparisonCategoryResult ValueKind) const { + const VarDecl *VD = lookupResultValue(ValueKind); + assert(VD && + "comparison category does not contain the specified result kind"); + return VD; + } + + const VarDecl *lookupResultValue(ComparisonCategoryResult ValueKind) const; + + /// \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 getResultValue(makeWeakResult(ComparisonCategoryResult::Equal)); + } + const VarDecl *getNonequalOrNonequiv() const { + return getResultValue(makeWeakResult(ComparisonCategoryResult::Nonequal)); + } + const VarDecl *getLess() const { + assert(isOrdered()); + return getResultValue(ComparisonCategoryResult::Less); + } + const VarDecl *getGreater() const { + assert(isOrdered()); + return getResultValue(ComparisonCategoryResult::Greater); + } + const VarDecl *getUnordered() const { + assert(isPartial()); + return getResultValue(ComparisonCategoryResult::Unordered); + } +}; + +class ComparisonCategories { +public: + static StringRef getCategoryString(ComparisonCategoryType Kind); + static StringRef getResultString(ComparisonCategoryResult Kind); + + /// \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)`. + /// + /// Note: The comparison category type must have already been built by Sema. + const ComparisonCategoryInfo &getInfoForType(QualType Ty) const; + +public: + /// \brief Return the comparison category information for the category + /// specified by 'Kind', or nullptr if it isn't available. + const ComparisonCategoryInfo *lookupInfo(ComparisonCategoryType Kind) const; + +private: + const ComparisonCategoryInfo *lookupInfoForType(QualType Ty) const; + NamespaceDecl *lookupStdNamespace() 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/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -9372,4 +9372,17 @@ 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 comparison category %0 is not supported; " + "failed to build reference to member '%1'">; } // end of sema component. Index: include/clang/Sema/Overload.h =================================================================== --- include/clang/Sema/Overload.h +++ include/clang/Sema/Overload.h @@ -330,9 +330,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; 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" @@ -4541,6 +4542,15 @@ CXXRecordDecl *getStdBadAlloc() const; EnumDecl *getStdAlignValT() const; + /// \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 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); 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,172 @@ +//===- 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" + +using namespace clang; + +const VarDecl *ComparisonCategoryInfo::lookupResultValue( + ComparisonCategoryResult ValueKind) const { + char Key = static_cast(ValueKind); + const 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 (const auto *VD = dyn_cast(ND)) { + auto ItPair = + Objects.try_emplace((char)ValueKind, const_cast(VD)); + return ItPair.first->second; + } + return nullptr; +} + +NamespaceDecl *ComparisonCategories::lookupStdNamespace() const { + if (!StdNS) { + IdentifierInfo &StdII = Ctx.Idents.get("std"); + DeclContextLookupResult Lookup = + Ctx.getTranslationUnitDecl()->lookup(&StdII); + if (Lookup.size() == 1) + StdNS = dyn_cast(Lookup.front()); + if (!StdNS) { + StdNS = NamespaceDecl::Create( + const_cast(Ctx), Ctx.getTranslationUnitDecl(), + /*Inline*/ false, SourceLocation(), SourceLocation(), &StdII, + /*PrevDecl*/ nullptr); + StdNS->setImplicit(true); + } + } + return StdNS; +} + +static RecordDecl *lookupRecordDecl(const ASTContext &Ctx, 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 (NamespaceDecl *NS = lookupStdNamespace()) { + 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; + const 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 = + const_cast(static_cast(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"); +} + 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"); } } @@ -3778,11 +3780,15 @@ }; } -static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { +static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD, + APValue *Dest = nullptr) { // We don't need to evaluate the initializer for a static local. if (!VD->hasLocalStorage()) return true; + if (Dest) + *Dest = APValue(); + LValue Result; APValue &Val = createTemporary(VD, true, Result, *Info.CurrentCall); @@ -3803,7 +3809,8 @@ Val = APValue(); return false; } - + if (Dest) + *Dest = Val; return true; } @@ -5059,7 +5066,7 @@ } }; -} +} // namespace //===----------------------------------------------------------------------===// // Common base class for lvalue and temporary evaluation. @@ -6232,6 +6239,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 +8224,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 +8504,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 +8577,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 +8598,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 +8627,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 +8660,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 +8669,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 +8676,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 +8687,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 +8702,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 +8733,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 +8743,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 +8768,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 +8794,162 @@ // 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) { + auto OnSuccess = [&](ComparisonCategoryResult ResKind, + const BinaryOperator *E) { + if (!CheckLiteralType(Info, E)) + return false; + + // 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.getResultValue(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, []() -> bool { + llvm_unreachable("operator<=> should have been evaluated to a result"); + }); +} + +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()) { + 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 || 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) { @@ -196,6 +198,7 @@ RValue Res = CGF.EmitAtomicExpr(E); EmitFinalDestCopy(E->getType(), Res); } + }; } // end anonymous namespace. @@ -879,6 +882,129 @@ 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 binary expression with complex arguments"); + if (ArgTy->isVectorType()) + return CGF.ErrorUnsupported( + E, "aggregate binary expression with vector arguments"); + if (!ArgTy->isIntegralOrEnumerationType() && !ArgTy->isRealFloatingType() && + !ArgTy->isPointerType() && !ArgTy->isMemberPointerType()) + return CGF.ErrorUnsupported(E, "aggregate binary expression"); + + 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) { + assert(VD->isUsed()); + DeclRefExpr DE(const_cast(VD), + /*RefersToEnclosingVariableOrCapture*/ false, E->getType(), + VK_LValue, E->getExprLoc()); + return CGF.EmitDeclRefLValue(&DE).getPointer(); + }; + 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"); + } + + 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/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" @@ -8884,6 +8885,88 @@ return StdExperimentalNamespaceCache; } +QualType Sema::CheckComparisonCategoryType(ComparisonCategoryType Kind, + SourceLocation Loc) { + using CCT = ComparisonCategoryType; + using CCVT = ComparisonCategoryResult; + 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. + const ComparisonCategoryInfo *CachedInfo = + Context.CompCategories.lookupInfo(Kind); + if (CachedInfo && CachedInfo->IsFullyChecked) + return QualType(CachedInfo->CCDecl->getTypeForDecl(), 0); + + StringRef Name = ComparisonCategories::getCategoryString(Kind); + // Build the initial category information + RecordDecl *CCDecl = nullptr; + // Lookup the record for the category type + if (auto Std = getStdNamespace()) { + LookupResult Result(*this, &PP.getIdentifierTable().get(Name), + SourceLocation(), Sema::LookupTagName); + if (LookupQualifiedName(Result, Std)) + CCDecl = Result.getAsSingle(); + Result.suppressDiagnostics(); + } + if (!CCDecl) { + Diag(Loc, diag::err_implied_comparison_category_type_not_found) << Name; + return QualType(); + } + + // Calculate the list of values belonging to this comparison category type. + SmallVector Values; + Values.push_back(CCVT::Equivalent); + bool IsStrong = (Kind == CCT::StrongEquality || Kind == CCT::StrongOrdering); + if (IsStrong) + Values.push_back(CCVT::Equal); + if (Kind == CCT::StrongOrdering || Kind == CCT::WeakOrdering || + Kind == CCT::PartialOrdering) { + Values.push_back(CCVT::Less); + Values.push_back(CCVT::Greater); + } else { + Values.push_back(CCVT::Nonequivalent); + if (IsStrong) + Values.push_back(CCVT::Nonequal); + } + if (Kind == CCT::PartialOrdering) + Values.push_back(CCVT::Unordered); + + // Build each of the require values and store them in Info. + for (CCVT CCV : Values) { + StringRef ValueName = ComparisonCategories::getResultString(CCV); + QualType Ty(CCDecl->getTypeForDecl(), 0); + DeclContext *LookupCtx = computeDeclContext(Ty); + LookupResult Found(*this, &PP.getIdentifierTable().get(ValueName), Loc, + Sema::LookupOrdinaryName); + if (!LookupQualifiedName(Found, LookupCtx)) { + Diag(Loc, diag::err_std_compare_type_missing_member) + << CCDecl << ValueName; + return QualType(); + } + auto *VD = Found.getAsSingle(); + + // 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 || !VD->isStaticDataMember() || !VD->isConstexpr()) { + Diag(Loc, diag::err_std_compare_type_not_supported) + << CCDecl << ValueName; + return QualType(); + } + MarkVariableReferenced(Loc, VD); + } + + // We've successfully built the required types and expressions. Update + // the cache and return the newly cached value. + assert(CachedInfo && "lookup of cached info should have succeeded"); + assert(CachedInfo->CCDecl == CCDecl && "ASTContext found different decl?"); + assert(CachedInfo->Kind == Kind); + CachedInfo->IsFullyChecked = true; + return QualType(CachedInfo->CCDecl->getTypeForDecl(), 0); +} + /// \brief Retrieve the special "std" namespace, which may require us to /// implicitly define the namespace. NamespaceDecl *Sema::getOrCreateStdNamespace() { 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" @@ -9703,10 +9704,152 @@ } } +static ImplicitConversionKind castKindToImplicitConversionKind(CastKind CK) { + switch (CK) { + default: + llvm_unreachable("unhandled cast kind"); + 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 checkNarrowingConversion(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: + 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. We'll diagnose this in a moment. + case NK_Type_Narrowing: + break; + } + + if (E->isValueDependent()) + return false; + + // Check the expression is a constant expression. + SmallVector Notes; + Expr::EvalResult Eval; + Eval.Diag = &Notes; + + if (ToType->isReferenceType() ? !E->EvaluateAsLValue(Eval, S.Context) + : !E->EvaluateAsRValue(Eval, S.Context)) { + // The expression can't be folded, so we can't keep it at this position in + // the AST. + } else if (Notes.empty()) { + // It's a constant expression. + return false; + } + S.Diag(E->getLocStart(), diag::err_spaceship_argument_narrowing) + << /*Constant*/ 0 << FromType << ToType; + + // TODO: It's not a constant expression. If the user intended it to be a + // constant expression, can we produce notes to help them figure out why it + // isn't? + + return true; +} + +static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S, + ExprResult &LHS, + ExprResult &RHS, + SourceLocation Loc) { + QualType LHSType = LHS.get()->getType(); + QualType RHSType = RHS.get()->getType(); + + // C++2a [expr.spaceship]p3 + if (int Count = (LHSType->isBooleanType() + RHSType->isBooleanType())) { + // TODO: The spec says that if one but not both of the operands is 'bool' + // the program is ill-formed. However, what about bool non-narrowing cases? + if (Count != 2) { + S.InvalidOperands(Loc, LHS, RHS); + return QualType(); + } + } + + QualType Type; + if (LHSType->isEnumeralType() || RHSType->isEnumeralType()) { + // C++2a [expr.spaceship]p5 + if (!S.Context.hasSameUnqualifiedType(LHSType, RHSType)) { + S.InvalidOperands(Loc, LHS, RHS); + return QualType(); + } + Type = LHSType->getAs()->getDecl()->getIntegerType(); + assert(Type->isArithmeticType()); + + LHS = S.ImpCastExprToType(LHS.get(), Type, CK_BitCast); + RHS = S.ImpCastExprToType(RHS.get(), Type, CK_BitCast); + } else { + // C++2a [expr.spaceship]p4 + Type = S.UsualArithmeticConversions(LHS, RHS); + if (LHS.isInvalid() || RHS.isInvalid()) + return QualType(); + if (Type.isNull()) + return S.InvalidOperands(Loc, LHS, RHS); + assert(Type->isArithmeticType()); + + bool HasNarrowing = checkNarrowingConversion(S, Type, LHS.get(), LHSType, + LHS.get()->getLocStart()); + HasNarrowing |= checkNarrowingConversion(S, Type, RHS.get(), RHSType, + RHS.get()->getLocStart()); + if (HasNarrowing) + return QualType(); + } + assert(!Type.isNull() && "composite type for <=> has not been set"); + + auto TypeKind = [&]() { + using CCK = ComparisonCategoryType; + if (Type->isIntegralOrEnumerationType()) + return CCK::StrongOrdering; + if (Type->hasFloatingRepresentation()) + return CCK::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()) @@ -9733,7 +9876,6 @@ 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(); } @@ -9741,6 +9883,7 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, BinaryOperatorKind Opc, bool IsRelational) { + bool IsThreeWayCmp = Opc == BO_Cmp; // Comparisons expect an rvalue, so convert to rvalue before any // type-related checks. LHS = DefaultFunctionArrayLvalueConversion(LHS.get()); @@ -9766,7 +9909,38 @@ (RHSType->isArithmeticType() || RHSType->isEnumeralType())) return checkArithmeticOrEnumeralCompare(*this, LHS, RHS, Loc, Opc); - QualType ResultTy = Context.getLogicalOperationType(); + auto computeResultTy = [&]() { + if (!IsThreeWayCmp) + 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()) + 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() && + (CompositeTy->getPointeeType()->isObjectType() || + CompositeTy->getPointeeType()->isVoidType())) + return buildResultTy(ComparisonCategoryType::StrongOrdering); + + // C++2a [expr.spaceship]p9: Otherwise, the program is ill-formed. + // TODO: Can this case actually occur? ie we have a + // non-object/function/mem-function pointer, non-enum, and non-integral type + return InvalidOperands(Loc, LHS, RHS); + }; const Expr::NullPointerConstantKind LHSNullKind = LHS.get()->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull); @@ -9807,24 +9981,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() || + ((IsRelational && !IsThreeWayCmp) ? 2 : 1) && + (!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 @@ -9839,7 +10014,7 @@ if (Context.typesAreCompatible(LCanPointeeTy.getUnqualifiedType(), RCanPointeeTy.getUnqualifiedType())) { // Valid unless a relational comparison of function pointers - if (IsRelational && LCanPointeeTy->isFunctionType()) { + if ((IsRelational && !IsThreeWayCmp) && LCanPointeeTy->isFunctionType()) { Diag(Loc, diag::ext_typecheck_ordered_comparison_of_function_pointers) << LHSType << RHSType << LHS.get()->getSourceRange() << RHS.get()->getSourceRange(); @@ -9875,21 +10050,21 @@ else RHS = ImpCastExprToType(RHS.get(), LHSType, Kind); } - return ResultTy; + return computeResultTy(); } if (getLangOpts().CPlusPlus) { // C++ [expr.eq]p4: // Two operands of type std::nullptr_t or one operand of type // std::nullptr_t and the other a null pointer constant compare equal. - if (!IsRelational && LHSIsNull && RHSIsNull) { + if ((!IsRelational || IsThreeWayCmp) && 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 +10073,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 +10101,7 @@ RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); else LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } } } @@ -9934,12 +10109,12 @@ // C++ [expr.eq]p2: // If at least one operand is a pointer to member, [...] bring them to // their composite pointer type. - if (!IsRelational && + if ((!IsRelational || IsThreeWayCmp) && (LHSType->isMemberPointerType() || RHSType->isMemberPointerType())) { if (convertPointersToCompositeType(*this, Loc, LHS, RHS)) return QualType(); else - return ResultTy; + return computeResultTy(); } } @@ -9956,7 +10131,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 +10155,7 @@ RHS = ImpCastExprToType(RHS.get(), LHSType, LHSType->isPointerType() ? CK_BitCast : CK_AnyPointerToBlockPointerCast); - return ResultTy; + return computeResultTy(); } if (LHSType->isObjCObjectPointerType() || @@ -10013,7 +10188,7 @@ RHS = ImpCastExprToType(E, LHSType, LPT ? CK_BitCast :CK_CPointerToObjCPointerCast); } - return ResultTy; + return computeResultTy(); } if (LHSType->isObjCObjectPointerType() && RHSType->isObjCObjectPointerType()) { @@ -10027,20 +10202,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 +10255,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(); } } @@ -11763,11 +11938,9 @@ ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc, false); 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; + assert(ResultTy.isNull() || ResultTy->getAsCXXRecordDecl()); break; case BO_And: checkObjCPointerIntrospection(*this, LHS, RHS, OpLoc); 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"); 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,113 @@ +// 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; +} 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,18 @@ +// 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 + +#ifndef HEADER +#define HEADER + +#include "Inputs/std-compare.h" +constexpr auto foo() { + return (42 <=> 101); +} + +#else + +// expected-no-diagnostics + +static_assert(foo() < 0); + +#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,7 +1,12 @@ // 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 + +#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; @@ -19,16 +24,16 @@ // 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)(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); @@ -105,7 +110,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 +118,21 @@ (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) { + (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')}} + + // 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 +149,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 +179,68 @@ (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_ordering); + + 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}} +} 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,179 @@ 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)); + } + { + using SO = std::strong_ordering; + auto EQ = SO::equal; + auto LESS = SO::less; + auto GREATER = SO::greater; + + int Arr[5] = {}; + using ArrRef = decltype((Arr)); + + int *P1 = (Arr + 1); + int *P12 = (Arr + 1); + int *P2 = (Arr + 2); + + auto eq = (Arr <=> (ArrRef)Arr); //(P1 <=> P12); + CHECK_TYPE(decltype(eq), SO); + CHECK(eq.test_eq(EQ)); + } + { // 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() { + (void)(LHS <=> RHS); // expected-note 1+ {{subexpression not valid in a constant expression}} + return true; +} +int dummy = 42; +int dummy2 = 101; + +// expected-error@+1 {{must be initialized by a constant expression}} +constexpr bool tc1 = test_constexpr(); // expected-note {{in call}} +// expected-error@+1 {{must be initialized by a constant expression}} +constexpr bool tc2 = test_constexpr<&dummy, nullptr>(); // expected-note {{in call}} + +// 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