From: Eric Fiselier Date: Mon, 7 May 2018 21:07:10 +0000 (+0000) Subject: [C++2a] Implement operator<=> CodeGen and ExprConstant X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=dee3c3481090c1bd66912b12235a9267d8ec6bcc;p=clang [C++2a] Implement operator<=> CodeGen and ExprConstant Summary: This patch tackles long hanging fruit for the builtin operator<=> expressions. It is currently needs some cleanup before landing, but I want to get some initial feedback. The main changes are: * Lookup, build, and store the required standard library types and expressions in `ASTContext`. By storing them in ASTContext we don't need to store (and duplicate) the required expressions in the BinaryOperator AST nodes. * Implement [expr.spaceship] checking, including diagnosing narrowing conversions. * Implement `ExprConstant` for builtin spaceship operators. * Implement builitin operator<=> support in `CodeGenAgg`. Initially I emitted the required comparisons using `ScalarExprEmitter::VisitBinaryOperator`, but this caused the operand expressions to be emitted once for every required cmp. * Implement [builtin.over] with modifications to support the intent of P0946R0. See the note on `BuiltinOperatorOverloadBuilder::addThreeWayArithmeticOverloads` for more information about the workaround. Reviewers: rsmith, aaron.ballman, majnemer, rnk, compnerd, rjmccall Reviewed By: rjmccall Subscribers: rjmccall, rsmith, aaron.ballman, junbuml, mgorny, cfe-commits Differential Revision: https://reviews.llvm.org/D45476 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@331677 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/ASTContext.h b/include/clang/AST/ASTContext.h index af39a1f357..ab2916e7e0 100644 --- a/include/clang/AST/ASTContext.h +++ b/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" @@ -1978,6 +1979,10 @@ public: 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. + ComparisonCategories CompCategories; + private: CanQualType getFromTargetType(unsigned Type) const; TypeInfo getTypeInfoImpl(const Type *T) const; diff --git a/include/clang/AST/ComparisonCategories.h b/include/clang/AST/ComparisonCategories.h new file mode 100644 index 0000000000..5b264c38db --- /dev/null +++ b/include/clang/AST/ComparisonCategories.h @@ -0,0 +1,255 @@ +//===- 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/APSInt.h" +#include "llvm/ADT/DenseMap.h" +#include +#include + +namespace llvm { + class StringRef; + class APSInt; +} + +namespace clang { + +class ASTContext; +class VarDecl; +class CXXRecordDecl; +class Sema; +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, + Last = Unordered +}; + +class ComparisonCategoryInfo { + friend class ComparisonCategories; + friend class Sema; + +public: + ComparisonCategoryInfo(const ASTContext &Ctx, CXXRecordDecl *RD, + ComparisonCategoryType Kind) + : Ctx(Ctx), Record(RD), Kind(Kind) {} + + struct ValueInfo { + ComparisonCategoryResult Kind; + VarDecl *VD; + + ValueInfo(ComparisonCategoryResult Kind, VarDecl *VD) + : Kind(Kind), VD(VD), HasValue(false) {} + + /// \brief True iff we've successfully evaluated the variable as a constant + /// expression and extracted its integer value. + bool hasValidIntValue() const { return HasValue; } + + /// \brief Get the constant integer value used by this variable to represent + /// the comparison category result type. + llvm::APSInt getIntValue() const { + assert(hasValidIntValue()); + return IntValue; + } + + void setIntValue(llvm::APSInt Val) { + IntValue = Val; + HasValue = true; + } + + private: + friend class ComparisonCategoryInfo; + llvm::APSInt IntValue; + bool HasValue : 1; + }; +private: + const ASTContext &Ctx; + + /// \brief A map containing the comparison category result decls from the + /// standard library. The key is a value of ComparisonCategoryResult. + mutable llvm::SmallVector< + ValueInfo, static_cast(ComparisonCategoryResult::Last) + 1> + Objects; + + /// \brief Lookup the ValueInfo struct for the specified ValueKind. If the + /// VarDecl for the value cannot be found, nullptr is returned. + /// + /// If the ValueInfo does not have a valid integer value the variable + /// is evaluated as a constant expression to determine that value. + ValueInfo *lookupValueInfo(ComparisonCategoryResult ValueKind) const; + +public: + /// \brief The declaration for the comparison category type from the + /// standard library. + // FIXME: Make this const + CXXRecordDecl *Record = nullptr; + + /// \brief The Kind of the comparison category type + ComparisonCategoryType Kind; + +public: + QualType getType() const; + + const ValueInfo *getValueInfo(ComparisonCategoryResult ValueKind) const { + ValueInfo *Info = lookupValueInfo(ValueKind); + assert(Info && + "comparison category does not contain the specified result kind"); + assert(Info->hasValidIntValue() && + "couldn't determine the integer constant for this value"); + return Info; + } + + /// \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 ValueInfo *getEqualOrEquiv() const { + return getValueInfo(makeWeakResult(ComparisonCategoryResult::Equal)); + } + const ValueInfo *getNonequalOrNonequiv() const { + assert(isEquality()); + return getValueInfo(makeWeakResult(ComparisonCategoryResult::Nonequal)); + } + const ValueInfo *getLess() const { + assert(isOrdered()); + return getValueInfo(ComparisonCategoryResult::Less); + } + const ValueInfo *getGreater() const { + assert(isOrdered()); + return getValueInfo(ComparisonCategoryResult::Greater); + } + const ValueInfo *getUnordered() const { + assert(isPartial()); + return getValueInfo(ComparisonCategoryResult::Unordered); + } +}; + +class ComparisonCategories { +public: + static StringRef getCategoryString(ComparisonCategoryType Kind); + static StringRef getResultString(ComparisonCategoryResult Kind); + + /// \brief Return the list of results which are valid for the specified + /// comparison category type. + static std::vector + getPossibleResultsForType(ComparisonCategoryType Type); + + /// \brief Return the comparison category information for the category + /// specified by 'Kind'. + const ComparisonCategoryInfo &getInfo(ComparisonCategoryType Kind) const { + const ComparisonCategoryInfo *Result = lookupInfo(Kind); + assert(Result != nullptr && + "information for specified comparison category has not been built"); + return *Result; + } + + /// \brief Return the comparison category information as specified by + /// `getCategoryForType(Ty)`. If the information is not already cached, + /// the declaration is looked up and a cache entry is created. + /// NOTE: Lookup is expected to succeed. Use lookupInfo if failure is possible. + const ComparisonCategoryInfo &getInfoForType(QualType Ty) const; + +public: + /// \brief Return the cached comparison category information for the + /// specified 'Kind'. If no cache entry is present the comparison category + /// type is looked up. If lookup fails nullptr is returned. Otherwise, a + /// new cache entry is created and returned + const ComparisonCategoryInfo *lookupInfo(ComparisonCategoryType Kind) const; + + ComparisonCategoryInfo *lookupInfo(ComparisonCategoryType Kind) { + const auto &This = *this; + return const_cast(This.lookupInfo(Kind)); + } + +private: + const ComparisonCategoryInfo *lookupInfoForType(QualType Ty) const; + +private: + friend class ASTContext; + + explicit ComparisonCategories(const ASTContext &Ctx) : Ctx(Ctx) {} + + const ASTContext &Ctx; + + /// A map from the ComparisonCategoryType (represented as 'char') to the + /// cached information for the specified category. + mutable llvm::DenseMap Data; + mutable NamespaceDecl *StdNS = nullptr; +}; + +} // namespace clang + +#endif diff --git a/include/clang/AST/Expr.h b/include/clang/AST/Expr.h index 32d16ac782..99e0302004 100644 --- a/include/clang/AST/Expr.h +++ b/include/clang/AST/Expr.h @@ -2778,7 +2778,9 @@ protected: public: CastKind getCastKind() const { return (CastKind) CastExprBits.Kind; } void setCastKind(CastKind K) { CastExprBits.Kind = K; } - const char *getCastKindName() const; + + static const char *getCastKindName(CastKind CK); + const char *getCastKindName() const { return getCastKindName(getCastKind()); } Expr *getSubExpr() { return cast(Op); } const Expr *getSubExpr() const { return cast(Op); } diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index a015f45feb..941dc8e32f 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -9424,4 +9424,18 @@ def err_multiversion_not_allowed_on_main : Error< 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_not_supported : Error< + "standard library implementation of %0 is not supported; " + "%select{member '%2' does not have expected form|" + "member '%2' is missing|" + "the type is not trivially copyable|" + "the type does not have the expected form}1">; } // end of sema component. diff --git a/include/clang/Sema/Overload.h b/include/clang/Sema/Overload.h index 5a49881f32..7dd23140a5 100644 --- a/include/clang/Sema/Overload.h +++ b/include/clang/Sema/Overload.h @@ -330,9 +330,10 @@ class Sema; } 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; diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 417ac260f0..172cf4363c 100644 --- a/include/clang/Sema/Sema.h +++ b/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" @@ -49,6 +50,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/TinyPtrVector.h" @@ -4545,6 +4547,22 @@ public: CXXRecordDecl *getStdBadAlloc() const; EnumDecl *getStdAlignValT() const; +private: + // A cache representing if we've fully checked the various comparison category + // types stored in ASTContext. The bit-index corresponds to the integer value + // of a ComparisonCategoryType enumerator. + llvm::SmallBitVector FullyCheckedComparisonCategories; + +public: + /// \brief Lookup the specified comparison category types in the standard + /// library, an check the VarDecls possibly returned by the operator<=> + /// builtins for that type. + /// + /// \return The type of the comparison category type corresponding to the + /// specified Kind, or a null type if an error occurs + QualType CheckComparisonCategoryType(ComparisonCategoryType Kind, + SourceLocation Loc); + /// \brief 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); @@ -9574,8 +9592,8 @@ public: ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, BinaryOperatorKind Opc, bool IsCompAssign = false); QualType CheckCompareOperands( // C99 6.5.8/9 - ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, - BinaryOperatorKind Opc, bool isRelational); + ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, + BinaryOperatorKind Opc); QualType CheckBitwiseOperands( // C99 6.5.[10...12] ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, BinaryOperatorKind Opc); diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index bf79e07f20..a0fc4e72ef 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -792,7 +792,8 @@ ASTContext::ASTContext(LangOptions &LOpts, SourceManager &SM, 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); } diff --git a/lib/AST/CMakeLists.txt b/lib/AST/CMakeLists.txt index a6f1027856..4f868a3af5 100644 --- a/lib/AST/CMakeLists.txt +++ b/lib/AST/CMakeLists.txt @@ -20,6 +20,7 @@ add_clang_library(clangAST CommentLexer.cpp CommentParser.cpp CommentSema.cpp + ComparisonCategories.cpp DataCollection.cpp Decl.cpp DeclarationName.cpp diff --git a/lib/AST/ComparisonCategories.cpp b/lib/AST/ComparisonCategories.cpp new file mode 100644 index 0000000000..c2bb3c653e --- /dev/null +++ b/lib/AST/ComparisonCategories.cpp @@ -0,0 +1,220 @@ +//===- ComparisonCategories.cpp - Three Way Comparison Data -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the Comparison Category enum and data types, which +// store the types and expressions needed to support operator<=> +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ComparisonCategories.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Type.h" +#include "llvm/ADT/SmallVector.h" + +using namespace clang; + +/// Attempt to determine the integer value used to represent the comparison +/// category result by evaluating the initializer for the specified VarDecl as +/// a constant expression and retreiving the value of the classes first +/// (and only) field. +/// +/// Note: The STL types are expected to have the form: +/// struct X { T value; }; +/// where T is an integral or enumeration type. +static bool evaluateIntValue(const ASTContext &Ctx, + ComparisonCategoryInfo::ValueInfo *Info) { + if (Info->hasValidIntValue()) + return false; + + // Before we attempt to get the value of the first field, ensure that we + // actually have one (and only one) field. + auto *Record = Info->VD->getType()->getAsCXXRecordDecl(); + if (std::distance(Record->field_begin(), Record->field_end()) != 1 || + !Record->field_begin()->getType()->isIntegralOrEnumerationType()) + return true; + + Expr::EvalResult Result; + if (!Info->VD->hasInit() || + !Info->VD->getInit()->EvaluateAsRValue(Result, Ctx)) + return true; + + assert(Result.Val.isStruct()); + Info->setIntValue(Result.Val.getStructField(0).getInt()); + return false; +} + +ComparisonCategoryInfo::ValueInfo *ComparisonCategoryInfo::lookupValueInfo( + ComparisonCategoryResult ValueKind) const { + // Check if we already have a cache entry for this value. + auto It = llvm::find_if( + Objects, [&](ValueInfo const &Info) { return Info.Kind == ValueKind; }); + + // We don't have a cached result. Lookup the variable declaration and create + // a new entry representing it. + if (It == Objects.end()) { + DeclContextLookupResult Lookup = Record->getCanonicalDecl()->lookup( + &Ctx.Idents.get(ComparisonCategories::getResultString(ValueKind))); + if (Lookup.size() != 1 || !isa(Lookup.front())) + return nullptr; + Objects.emplace_back(ValueKind, cast(Lookup.front())); + It = Objects.end() - 1; + } + assert(It != Objects.end()); + // Success! Attempt to update the int value in case the variables initializer + // wasn't present the last time we were here. + ValueInfo *Info = &(*It); + evaluateIntValue(Ctx, Info); + + return Info; +} + +static const NamespaceDecl *lookupStdNamespace(const ASTContext &Ctx, + NamespaceDecl *&StdNS) { + if (!StdNS) { + DeclContextLookupResult Lookup = + Ctx.getTranslationUnitDecl()->lookup(&Ctx.Idents.get("std")); + if (Lookup.size() == 1) + StdNS = dyn_cast(Lookup.front()); + } + return StdNS; +} + +static CXXRecordDecl *lookupCXXRecordDecl(const ASTContext &Ctx, + const NamespaceDecl *StdNS, + ComparisonCategoryType Kind) { + StringRef Name = ComparisonCategories::getCategoryString(Kind); + DeclContextLookupResult Lookup = StdNS->lookup(&Ctx.Idents.get(Name)); + if (Lookup.size() == 1) + if (CXXRecordDecl *RD = dyn_cast(Lookup.front())) + return RD; + return nullptr; +} + +const ComparisonCategoryInfo * +ComparisonCategories::lookupInfo(ComparisonCategoryType Kind) const { + auto It = Data.find(static_cast(Kind)); + if (It != Data.end()) + return &It->second; + + if (const NamespaceDecl *NS = lookupStdNamespace(Ctx, StdNS)) + if (CXXRecordDecl *RD = lookupCXXRecordDecl(Ctx, NS, Kind)) + return &Data.try_emplace((char)Kind, Ctx, RD, Kind).first->second; + + return nullptr; +} + +const ComparisonCategoryInfo * +ComparisonCategories::lookupInfoForType(QualType Ty) const { + assert(!Ty.isNull() && "type must be non-null"); + using CCT = ComparisonCategoryType; + auto *RD = Ty->getAsCXXRecordDecl(); + if (!RD) + return nullptr; + + // Check to see if we have information for the specified type cached. + const auto *CanonRD = RD->getCanonicalDecl(); + for (auto &KV : Data) { + const ComparisonCategoryInfo &Info = KV.second; + if (CanonRD == Info.Record->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()) + return &Data.try_emplace((char)Kind, Ctx, RD, Kind).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; +} + +QualType ComparisonCategoryInfo::getType() const { + assert(Record); + return QualType(Record->getTypeForDecl(), 0); +} + +StringRef ComparisonCategories::getCategoryString(ComparisonCategoryType Kind) { + using CCKT = ComparisonCategoryType; + switch (Kind) { + case CCKT::WeakEquality: + return "weak_equality"; + case CCKT::StrongEquality: + return "strong_equality"; + case CCKT::PartialOrdering: + return "partial_ordering"; + case CCKT::WeakOrdering: + return "weak_ordering"; + case CCKT::StrongOrdering: + return "strong_ordering"; + } + llvm_unreachable("unhandled cases in switch"); +} + +StringRef ComparisonCategories::getResultString(ComparisonCategoryResult Kind) { + using CCVT = ComparisonCategoryResult; + switch (Kind) { + case CCVT::Equal: + return "equal"; + case CCVT::Nonequal: + return "nonequal"; + case CCVT::Equivalent: + return "equivalent"; + case CCVT::Nonequivalent: + return "nonequivalent"; + case CCVT::Less: + return "less"; + case CCVT::Greater: + return "greater"; + case CCVT::Unordered: + return "unordered"; + } + llvm_unreachable("unhandled case in switch"); +} + +std::vector +ComparisonCategories::getPossibleResultsForType(ComparisonCategoryType Type) { + using CCT = ComparisonCategoryType; + using CCR = ComparisonCategoryResult; + std::vector Values; + Values.reserve(6); + Values.push_back(CCR::Equivalent); + bool IsStrong = (Type == CCT::StrongEquality || Type == CCT::StrongOrdering); + if (IsStrong) + Values.push_back(CCR::Equal); + if (Type == CCT::StrongOrdering || Type == CCT::WeakOrdering || + Type == CCT::PartialOrdering) { + Values.push_back(CCR::Less); + Values.push_back(CCR::Greater); + } else { + Values.push_back(CCR::Nonequivalent); + if (IsStrong) + Values.push_back(CCR::Nonequal); + } + if (Type == CCT::PartialOrdering) + Values.push_back(CCR::Unordered); + return Values; +} diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 57c8850ba9..b2def80234 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -1633,8 +1633,8 @@ bool CastExpr::CastConsistency() const { return true; } -const char *CastExpr::getCastKindName() const { - switch (getCastKind()) { +const char *CastExpr::getCastKindName(CastKind CK) { + switch (CK) { #define CAST_OPERATION(Name) case CK_##Name: return #Name; #include "clang/AST/OperationKinds.def" } diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index c540dfbbf0..ea596d05f6 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -2242,6 +2242,8 @@ static bool handleIntIntBinOp(EvalInfo &Info, const Expr *E, const APSInt &LHS, 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"); } } @@ -5059,7 +5061,7 @@ public: } }; -} +} // namespace //===----------------------------------------------------------------------===// // Common base class for lvalue and temporary evaluation. @@ -6232,6 +6234,8 @@ namespace { bool VisitCXXInheritedCtorInitExpr(const CXXInheritedCtorInitExpr *E); bool VisitCXXConstructExpr(const CXXConstructExpr *E, QualType T); bool VisitCXXStdInitializerListExpr(const CXXStdInitializerListExpr *E); + + bool VisitBinCmp(const BinaryOperator *E); }; } @@ -7072,11 +7076,11 @@ bool ArrayExprEvaluator::VisitCXXConstructExpr(const CXXConstructExpr *E, namespace { class IntExprEvaluator - : public ExprEvaluatorBase { + : public ExprEvaluatorBase { APValue &Result; public: IntExprEvaluator(EvalInfo &info, APValue &result) - : ExprEvaluatorBaseTy(info), Result(result) {} + : ExprEvaluatorBaseTy(info), Result(result) {} bool Success(const llvm::APSInt &SI, const Expr *E, APValue &Result) { assert(E->getType()->isIntegralOrEnumerationType() && @@ -7107,7 +7111,7 @@ public: } bool Success(uint64_t Value, const Expr *E, APValue &Result) { - assert(E->getType()->isIntegralOrEnumerationType() && + assert(E->getType()->isIntegralOrEnumerationType() && "Invalid evaluation result."); Result = APValue(Info.Ctx.MakeIntValue(Value, E->getType())); return true; @@ -8226,10 +8230,8 @@ public: /// 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()); } @@ -8508,19 +8510,47 @@ public: }; } -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; @@ -8553,30 +8583,13 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { 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); } } @@ -8591,243 +8604,160 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { 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); - 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)) { - 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()) - 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 - // to a null pointer constant. - if ((!LHSValue.Base && !LHSValue.Offset.isZero()) || - (!RHSValue.Base && !RHSValue.Offset.isZero())) - return Error(E); - // It's implementation-defined whether distinct literals will have - // distinct addresses. In clang, the result of such a comparison is - // unspecified, so it is not a constant expression. However, we do know - // that the address of a literal will be non-null. - if ((IsLiteralLValue(LHSValue) || IsLiteralLValue(RHSValue)) && - LHSValue.Base && RHSValue.Base) - return Error(E); - // We can't tell whether weak symbols will end up pointing to the same - // object. - if (IsWeakLValue(LHSValue) || IsWeakLValue(RHSValue)) - return Error(E); - // We can't compare the address of the start of one object with the - // past-the-end address of another object, per C++ DR1652. - if ((LHSValue.Base && LHSValue.Offset.isZero() && - isOnePastTheEndOfCompleteObject(Info.Ctx, RHSValue)) || - (RHSValue.Base && RHSValue.Offset.isZero() && - isOnePastTheEndOfCompleteObject(Info.Ctx, LHSValue))) - return Error(E); - // We can't tell whether an object is at the same address as another - // zero sized object. - 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); - } + LValue LHSValue, RHSValue; - const CharUnits &LHSOffset = LHSValue.getLValueOffset(); - const CharUnits &RHSOffset = RHSValue.getLValueOffset(); - - 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; - } + bool LHSOK = EvaluatePointer(E->getLHS(), LHSValue, Info); + if (!LHSOK && !Info.noteFailure()) + 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); - } + if (!EvaluatePointer(E->getRHS(), RHSValue, Info) || !LHSOK) + return false; - // 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 - // address or are both the null pointer value, the result is true if the - // 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); - - // C++11 [expr.rel]p2: - // - If two pointers point to non-static data members of the same object, - // or to subobjects or array elements fo such members, recursively, the - // pointer to the later declared member compares greater provided the - // two members have the same access control and provided their class is - // not a union. - // [...] - // - Otherwise pointer comparisons are unspecified. - if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && - E->isRelationalOp()) { - bool 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 - // - we are comparing fields of a union, or fields with the same access - // Otherwise, the result is unspecified and thus the comparison is not a - // constant expression. - if (!WasArrayIndex && Mismatch < LHSDesignator.Entries.size() && - Mismatch < RHSDesignator.Entries.size()) { - 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); - else if (!LF) - CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) + // Reject differing bases from the normal codepath; we special-case + // comparisons to null. + if (!HasSameBase(LHSValue, RHSValue)) { + // Inequalities and subtractions between unrelated pointers have + // unspecified or undefined behavior. + 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 + // to a null pointer constant. + if ((!LHSValue.Base && !LHSValue.Offset.isZero()) || + (!RHSValue.Base && !RHSValue.Offset.isZero())) + return Error(E); + // It's implementation-defined whether distinct literals will have + // distinct addresses. In clang, the result of such a comparison is + // unspecified, so it is not a constant expression. However, we do know + // that the address of a literal will be non-null. + if ((IsLiteralLValue(LHSValue) || IsLiteralLValue(RHSValue)) && + LHSValue.Base && RHSValue.Base) + return Error(E); + // We can't tell whether weak symbols will end up pointing to the same + // object. + if (IsWeakLValue(LHSValue) || IsWeakLValue(RHSValue)) + return Error(E); + // We can't compare the address of the start of one object with the + // past-the-end address of another object, per C++ DR1652. + if ((LHSValue.Base && LHSValue.Offset.isZero() && + isOnePastTheEndOfCompleteObject(Info.Ctx, RHSValue)) || + (RHSValue.Base && RHSValue.Offset.isZero() && + isOnePastTheEndOfCompleteObject(Info.Ctx, LHSValue))) + return Error(E); + // We can't tell whether an object is at the same address as another + // zero sized object. + if ((RHSValue.Base && isZeroSized(LHSValue)) || + (LHSValue.Base && isZeroSized(RHSValue))) + return Error(E); + return Success(CCR::Nonequal, E); + } + + const CharUnits &LHSOffset = LHSValue.getLValueOffset(); + const CharUnits &RHSOffset = RHSValue.getLValueOffset(); + + SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator(); + SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator(); + + // 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 + // address or are both the null pointer value, the result is true if the + // 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 && 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, + // or to subobjects or array elements fo such members, recursively, the + // pointer to the later declared member compares greater provided the + // two members have the same access control and provided their class is + // not a union. + // [...] + // - Otherwise pointer comparisons are unspecified. + if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && IsRelational) { + bool 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 + // - we are comparing fields of a union, or fields with the same access + // Otherwise, the result is unspecified and thus the comparison is not a + // constant expression. + if (!WasArrayIndex && Mismatch < LHSDesignator.Entries.size() && + Mismatch < RHSDesignator.Entries.size()) { + const FieldDecl *LF = getAsField(LHSDesignator.Entries[Mismatch]); + const FieldDecl *RF = getAsField(RHSDesignator.Entries[Mismatch]); + if (!LF && !RF) + Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_classes); + else if (!LF) + 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) + else if (!RF) + 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) + else if (!LF->getParent()->isUnion() && + LF->getAccess() != RF->getAccess()) + Info.CCEDiag(E, + diag::note_constexpr_pointer_comparison_differing_access) << LF << LF->getAccess() << RF << RF->getAccess() << LF->getParent(); - } - } - - // The comparison here must be unsigned, and performed with the same - // width as the pointer. - unsigned PtrSize = Info.Ctx.getTypeSize(LHSTy); - uint64_t CompareLHS = LHSOffset.getQuantity(); - uint64_t CompareRHS = RHSOffset.getQuantity(); - assert(PtrSize <= 64 && "Unexpected pointer width"); - uint64_t Mask = ~0ULL >> (64 - PtrSize); - CompareLHS &= Mask; - CompareRHS &= Mask; - - // 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()) { - QualType BaseTy = getType(LHSValue.Base); - if (BaseTy->isIncompleteType()) - return Error(E); - CharUnits Size = Info.Ctx.getTypeSizeInChars(BaseTy); - uint64_t OffsetLimit = Size.getQuantity(); - if (CompareLHS > OffsetLimit || CompareRHS > OffsetLimit) - 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); - } + // The comparison here must be unsigned, and performed with the same + // width as the pointer. + unsigned PtrSize = Info.Ctx.getTypeSize(LHSTy); + uint64_t CompareLHS = LHSOffset.getQuantity(); + uint64_t CompareRHS = RHSOffset.getQuantity(); + assert(PtrSize <= 64 && "Unexpected pointer width"); + uint64_t Mask = ~0ULL >> (64 - PtrSize); + CompareLHS &= Mask; + CompareRHS &= Mask; + + // 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() && IsRelational) { + QualType BaseTy = getType(LHSValue.Base); + if (BaseTy->isIncompleteType()) + return Error(E); + CharUnits Size = Info.Ctx.getTypeSizeInChars(BaseTy); + uint64_t OffsetLimit = Size.getQuantity(); + if (CompareLHS > OffsetLimit || CompareRHS > OffsetLimit) + return Error(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; @@ -8844,24 +8774,24 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { // 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()) { @@ -8870,14 +8800,163 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { // C++11 [expr.rel]p4, [expr.eq]p3: If two operands of type std::nullptr_t // are compared, the result is true of the operator is <=, >= or ==, and // false otherwise. - BinaryOperator::Opcode Opcode = E->getOpcode(); - return Success(Opcode == BO_EQ || Opcode == BO_LE || Opcode == BO_GE, E); + return Success(CCR::Equal, E); } - assert((!LHSTy->isIntegralOrEnumerationType() || - !RHSTy->isIntegralOrEnumerationType()) && + return DoAfter(); +} + +bool RecordExprEvaluator::VisitBinCmp(const BinaryOperator *E) { + if (!CheckLiteralType(Info, E)) + return false; + + auto OnSuccess = [&](ComparisonCategoryResult ResKind, + const BinaryOperator *E) { + // Evaluation succeeded. Lookup the information for the comparison category + // type and fetch the VarDecl for the result. + const ComparisonCategoryInfo &CmpInfo = + Info.Ctx.CompCategories.getInfoForType(E->getType()); + const VarDecl *VD = + CmpInfo.getValueInfo(CmpInfo.makeWeakResult(ResKind))->VD; + // Check and evaluate the result as a constant expression. + LValue LV; + LV.set(VD); + if (!handleLValueToRValueConversion(Info, E, E->getType(), LV, Result)) + return false; + return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result); + }; + return EvaluateComparisonBinaryOperator(Info, E, OnSuccess, [&]() { + return ExprEvaluatorBaseTy::VisitBinCmp(E); + }); +} + +bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { + // We don't call noteFailure immediately because the assignment happens after + // we evaluate LHS and RHS. + if (!Info.keepEvaluatingAfterFailure() && E->isAssignmentOp()) + return Error(E); + + DelayedNoteFailureRAII MaybeNoteFailureLater(Info, E->isAssignmentOp()); + if (DataRecursiveIntBinOpEvaluator::shouldEnqueue(E)) + return DataRecursiveIntBinOpEvaluator(*this, Result).Traverse(E); + + assert((!E->getLHS()->getType()->isIntegralOrEnumerationType() || + !E->getRHS()->getType()->isIntegralOrEnumerationType()) && "DataRecursiveIntBinOpEvaluator should have handled integral types"); - // We can't continue from here for non-integral types. + + if (E->isComparisonOp()) { + // Evaluate builtin binary comparisons by evaluating them as C++2a three-way + // comparisons and then translating the result. + auto OnSuccess = [&](ComparisonCategoryResult ResKind, + const BinaryOperator *E) { + using CCR = ComparisonCategoryResult; + bool IsEqual = ResKind == CCR::Equal, + IsLess = ResKind == CCR::Less, + IsGreater = ResKind == CCR::Greater; + auto Op = E->getOpcode(); + switch (Op) { + default: + llvm_unreachable("unsupported binary operator"); + case BO_EQ: + case BO_NE: + return Success(IsEqual == (Op == BO_EQ), E); + case BO_LT: return Success(IsLess, E); + case BO_GT: return Success(IsGreater, E); + case BO_LE: return Success(IsEqual || IsLess, E); + case BO_GE: return Success(IsEqual || IsGreater, E); + } + }; + return EvaluateComparisonBinaryOperator(Info, E, OnSuccess, [&]() { + return ExprEvaluatorBaseTy::VisitBinaryOperator(E); + }); + } + + QualType LHSTy = E->getLHS()->getType(); + QualType RHSTy = E->getRHS()->getType(); + + if (LHSTy->isPointerType() && RHSTy->isPointerType() && + E->getOpcode() == BO_Sub) { + LValue LHSValue, RHSValue; + + bool LHSOK = EvaluatePointer(E->getLHS(), LHSValue, Info); + if (!LHSOK && !Info.noteFailure()) + return false; + + if (!EvaluatePointer(E->getRHS(), RHSValue, Info) || !LHSOK) + return false; + + // Reject differing bases from the normal codepath; we special-case + // comparisons to null. + if (!HasSameBase(LHSValue, RHSValue)) { + // Handle &&A - &&B. + if (!LHSValue.Offset.isZero() || !RHSValue.Offset.isZero()) + return Error(E); + const Expr *LHSExpr = LHSValue.Base.dyn_cast(); + const Expr *RHSExpr = RHSValue.Base.dyn_cast(); + if (!LHSExpr || !RHSExpr) + return Error(E); + const AddrLabelExpr *LHSAddrExpr = dyn_cast(LHSExpr); + const AddrLabelExpr *RHSAddrExpr = dyn_cast(RHSExpr); + if (!LHSAddrExpr || !RHSAddrExpr) + return Error(E); + // Make sure both labels come from the same function. + if (LHSAddrExpr->getLabel()->getDeclContext() != + RHSAddrExpr->getLabel()->getDeclContext()) + return Error(E); + return Success(APValue(LHSAddrExpr, RHSAddrExpr), E); + } + const CharUnits &LHSOffset = LHSValue.getLValueOffset(); + const CharUnits &RHSOffset = RHSValue.getLValueOffset(); + + SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator(); + SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator(); + + // C++11 [expr.add]p6: + // Unless both pointers point to elements of the same array object, or + // one past the last element of the array object, the behavior is + // undefined. + if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && + !AreElementsOfSameArray(getType(LHSValue.Base), LHSDesignator, + RHSDesignator)) + Info.CCEDiag(E, diag::note_constexpr_pointer_subtraction_not_same_array); + + QualType Type = E->getLHS()->getType(); + QualType ElementType = Type->getAs()->getPointeeType(); + + CharUnits ElementSize; + if (!HandleSizeof(Info, E->getExprLoc(), ElementType, ElementSize)) + return false; + + // As an extension, a type may have zero size (empty struct or union in + // C, array of zero length). Pointer subtraction in such cases has + // undefined behavior, so is not constant. + if (ElementSize.isZero()) { + Info.FFDiag(E, diag::note_constexpr_pointer_subtraction_zero_size) + << ElementType; + return false; + } + + // FIXME: LLVM and GCC both compute LHSOffset - RHSOffset at runtime, + // and produce incorrect results when it overflows. Such behavior + // appears to be non-conforming, but is common, so perhaps we should + // assume the standard intended for such cases to be undefined behavior + // and check for them. + + // Compute (LHSOffset - RHSOffset) / Size carefully, checking for + // overflow in the final conversion to ptrdiff_t. + APSInt LHS(llvm::APInt(65, (int64_t)LHSOffset.getQuantity(), true), false); + APSInt RHS(llvm::APInt(65, (int64_t)RHSOffset.getQuantity(), true), false); + APSInt ElemSize(llvm::APInt(65, (int64_t)ElementSize.getQuantity(), true), + false); + APSInt TrueResult = (LHS - RHS) / ElemSize; + APSInt Result = TrueResult.trunc(Info.Ctx.getIntWidth(E->getType())); + + if (Result.extend(65) != TrueResult && + !HandleOverflow(Info, E, TrueResult, E->getType())) + return false; + return Success(Result, E); + } + return ExprEvaluatorBaseTy::VisitBinaryOperator(E); } @@ -10620,7 +10699,6 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) { 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. @@ -10642,7 +10720,8 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) { 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 || diff --git a/lib/CodeGen/CGExprAgg.cpp b/lib/CodeGen/CGExprAgg.cpp index 6693ddd747..23d74dae4e 100644 --- a/lib/CodeGen/CGExprAgg.cpp +++ b/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 @@ public: void VisitPointerToDataMemberBinaryOperator(const BinaryOperator *BO); void VisitBinAssign(const BinaryOperator *E); void VisitBinComma(const BinaryOperator *E); + void VisitBinCmp(const BinaryOperator *E); void VisitObjCMessageExpr(ObjCMessageExpr *E); void VisitObjCIvarRefExpr(ObjCIvarRefExpr *E) { @@ -879,6 +881,149 @@ void AggExprEmitter::VisitStmtExpr(const StmtExpr *E) { 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, + const char *NameSuffix = "") { + QualType ArgTy = E->getLHS()->getType(); + if (const ComplexType *CT = ArgTy->getAs()) + ArgTy = CT->getElementType(); + + 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->hasFloatingRepresentation()) + return Builder.CreateFCmp(InstInfo.FCmp, LHS, RHS, + llvm::Twine(InstInfo.Name) + NameSuffix); + if (ArgTy->isIntegralOrEnumerationType() || ArgTy->isPointerType()) { + auto Inst = + ArgTy->hasSignedIntegerRepresentation() ? InstInfo.SCmp : InstInfo.UCmp; + return Builder.CreateICmp(Inst, LHS, RHS, + llvm::Twine(InstInfo.Name) + NameSuffix); + } + + 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()); + assert(CmpInfo.Record->isTriviallyCopyable() && + "cannot copy non-trivially copyable aggregate"); + + QualType ArgTy = E->getLHS()->getType(); + + // TODO: Handle comparing these types. + if (ArgTy->isVectorType()) + return CGF.ErrorUnsupported( + E, "aggregate three-way comparison with vector arguments"); + if (!ArgTy->isIntegralOrEnumerationType() && !ArgTy->isRealFloatingType() && + !ArgTy->isNullPtrType() && !ArgTy->isPointerType() && + !ArgTy->isMemberPointerType() && !ArgTy->isAnyComplexType()) { + return CGF.ErrorUnsupported(E, "aggregate three-way comparisoaoeun"); + } + bool IsComplex = ArgTy->isAnyComplexType(); + + // Evaluate the operands to the expression and extract their values. + auto EmitOperand = [&](Expr *E) -> std::pair { + RValue RV = CGF.EmitAnyExpr(E); + if (RV.isScalar()) + return {RV.getScalarVal(), nullptr}; + if (RV.isAggregate()) + return {RV.getAggregatePointer(), nullptr}; + assert(RV.isComplex()); + return RV.getComplexVal(); + }; + auto LHSValues = EmitOperand(E->getLHS()), + RHSValues = EmitOperand(E->getRHS()); + + auto EmitCmp = [&](CompareKind K) { + Value *Cmp = EmitCompare(Builder, CGF, E, LHSValues.first, RHSValues.first, + K, IsComplex ? ".r" : ""); + if (!IsComplex) + return Cmp; + assert(K == CompareKind::CK_Equal); + Value *CmpImag = EmitCompare(Builder, CGF, E, LHSValues.second, + RHSValues.second, K, ".i"); + return Builder.CreateAnd(Cmp, CmpImag, "and.eq"); + }; + auto EmitCmpRes = [&](const ComparisonCategoryInfo::ValueInfo *VInfo) { + return Builder.getInt(VInfo->getIntValue()); + }; + + Value *Select; + if (ArgTy->isNullPtrType()) { + Select = EmitCmpRes(CmpInfo.getEqualOrEquiv()); + } else 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"); + } + // Create the return value in the destination slot. + EnsureDest(E->getType()); + LValue DestLV = CGF.MakeAddrLValue(Dest.getAddress(), E->getType()); + + // Emit the address of the first (and only) field in the comparison category + // type, and initialize it from the constant integer value selected above. + LValue FieldLV = CGF.EmitLValueForFieldInitialization( + DestLV, *CmpInfo.Record->field_begin()); + CGF.EmitStoreThroughLValue(RValue::get(Select), FieldLV, /*IsInit*/ true); + + // All done! The result is in the Dest slot. +} + void AggExprEmitter::VisitBinaryOperator(const BinaryOperator *E) { if (E->getOpcode() == BO_PtrMemD || E->getOpcode() == BO_PtrMemI) VisitPointerToDataMemberBinaryOperator(E); diff --git a/lib/Sema/Sema.cpp b/lib/Sema/Sema.cpp index 5c0026cd37..b964cb2f0e 100644 --- a/lib/Sema/Sema.cpp +++ b/lib/Sema/Sema.cpp @@ -137,10 +137,13 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, ValueWithBytesObjCTypeMethod(nullptr), NSArrayDecl(nullptr), ArrayWithObjectsMethod(nullptr), NSDictionaryDecl(nullptr), DictionaryWithObjectsMethod(nullptr), GlobalNewDeleteDeclared(false), - TUKind(TUKind), NumSFINAEErrors(0), AccessCheckingSFINAE(false), - InNonInstantiationSFINAEContext(false), NonInstantiationEntries(0), - ArgumentPackSubstitutionIndex(-1), CurrentInstantiationScope(nullptr), - DisableTypoCorrection(false), TyposCorrected(0), AnalysisWarnings(*this), + TUKind(TUKind), NumSFINAEErrors(0), + FullyCheckedComparisonCategories( + static_cast(ComparisonCategoryType::Last) + 1), + AccessCheckingSFINAE(false), InNonInstantiationSFINAEContext(false), + NonInstantiationEntries(0), ArgumentPackSubstitutionIndex(-1), + CurrentInstantiationScope(nullptr), DisableTypoCorrection(false), + TyposCorrected(0), AnalysisWarnings(*this), ThreadSafetyDeclCache(nullptr), VarDataSharingAttributesStack(nullptr), CurScope(nullptr), Ident_super(nullptr), Ident___float128(nullptr) { TUScope = nullptr; diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index 9126f27b4d..fb168957bd 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/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" @@ -8891,6 +8892,134 @@ NamespaceDecl *Sema::lookupStdExperimentalNamespace() { return StdExperimentalNamespaceCache; } +namespace { + +enum UnsupportedSTLSelect { + USS_InvalidMember, + USS_MissingMember, + USS_NonTrivial, + USS_Other +}; + +struct InvalidSTLDiagnoser { + Sema &S; + SourceLocation Loc; + QualType TyForDiags; + + QualType operator()(UnsupportedSTLSelect Sel = USS_Other, StringRef Name = "", + const VarDecl *VD = nullptr) { + { + auto D = S.Diag(Loc, diag::err_std_compare_type_not_supported) + << TyForDiags << ((int)Sel); + if (Sel == USS_InvalidMember || Sel == USS_MissingMember) { + assert(!Name.empty()); + D << Name; + } + } + if (Sel == USS_InvalidMember) { + S.Diag(VD->getLocation(), diag::note_var_declared_here) + << VD << VD->getSourceRange(); + } + return QualType(); + } +}; +} // namespace + +QualType Sema::CheckComparisonCategoryType(ComparisonCategoryType Kind, + SourceLocation Loc) { + assert(getLangOpts().CPlusPlus && + "Looking for comparison category type outside of C++."); + + // Check if we've already successfully checked the comparison category type + // before. If so, skip checking it again. + ComparisonCategoryInfo *Info = Context.CompCategories.lookupInfo(Kind); + if (Info && FullyCheckedComparisonCategories[static_cast(Kind)]) + return Info->getType(); + + // If lookup failed + if (!Info) { + Diag(Loc, diag::err_implied_comparison_category_type_not_found) + << ComparisonCategories::getCategoryString(Kind); + return QualType(); + } + + assert(Info->Kind == Kind); + assert(Info->Record); + + // Update the Record decl in case we encountered a forward declaration on our + // first pass. FIXME(EricWF): This is a bit of a hack. + if (Info->Record->hasDefinition()) + Info->Record = Info->Record->getDefinition(); + + // Use an elaborated type for diagnostics which has a name containing the + // prepended 'std' namespace but not any inline namespace names. + QualType TyForDiags = [&]() { + auto *NNS = + NestedNameSpecifier::Create(Context, nullptr, getStdNamespace()); + return Context.getElaboratedType(ETK_None, NNS, Info->getType()); + }(); + + if (RequireCompleteType(Loc, TyForDiags, diag::err_incomplete_type)) + return QualType(); + + InvalidSTLDiagnoser UnsupportedSTLError{*this, Loc, TyForDiags}; + + if (!Info->Record->isTriviallyCopyable()) + return UnsupportedSTLError(USS_NonTrivial); + + for (const CXXBaseSpecifier &BaseSpec : Info->Record->bases()) { + CXXRecordDecl *Base = BaseSpec.getType()->getAsCXXRecordDecl(); + // Tolerate empty base classes. + if (Base->isEmpty()) + continue; + // Reject STL implementations which have at least one non-empty base. + return UnsupportedSTLError(); + } + + // Check that the STL has implemented the types using a single integer field. + // This expectation allows better codegen for builtin operators. We require: + // (1) The class has exactly one field. + // (2) The field is an integral or enumeration type. + auto FIt = Info->Record->field_begin(), FEnd = Info->Record->field_end(); + if (std::distance(FIt, FEnd) != 1 || + !FIt->getType()->isIntegralOrEnumerationType()) { + return UnsupportedSTLError(); + } + + // Build each of the require values and store them in Info. + for (ComparisonCategoryResult CCR : + ComparisonCategories::getPossibleResultsForType(Kind)) { + StringRef MemName = ComparisonCategories::getResultString(CCR); + ComparisonCategoryInfo::ValueInfo *ValInfo = Info->lookupValueInfo(CCR); + + if (!ValInfo) + return UnsupportedSTLError(USS_MissingMember, MemName); + + VarDecl *VD = ValInfo->VD; + assert(VD && "should not be null!"); + + // Attempt to diagnose reasons why the STL definition of this type + // might be foobar, including it failing to be a constant expression. + // TODO Handle more ways the lookup or result can be invalid. + if (!VD->isStaticDataMember() || !VD->isConstexpr() || !VD->hasInit() || + !VD->checkInitIsICE()) + return UnsupportedSTLError(USS_InvalidMember, MemName, VD); + + // Attempt to evaluate the var decl as a constant expression and extract + // the value of its first field as a ICE. If this fails, the STL + // implementation is not supported. + if (!ValInfo->hasValidIntValue()) + return UnsupportedSTLError(); + + MarkVariableReferenced(Loc, VD); + } + + // We've successfully built the required types and expressions. Update + // the cache and return the newly cached value. + FullyCheckedComparisonCategories[static_cast(Kind)] = true; + return Info->getType(); +} + /// \brief Retrieve the special "std" namespace, which may require us to /// implicitly define the namespace. NamespaceDecl *Sema::getOrCreateStdNamespace() { diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 58e70a4cea..177cfd7354 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/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" @@ -9619,12 +9620,18 @@ static void diagnoseTautologicalComparison(Sema &S, SourceLocation Loc, Expr *RHSStripped = RHS->IgnoreParenImpCasts(); QualType LHSType = LHS->getType(); + QualType RHSType = RHS->getType(); if (LHSType->hasFloatingRepresentation() || (LHSType->isBlockPointerType() && !BinaryOperator::isEqualityOp(Opc)) || LHS->getLocStart().isMacroID() || RHS->getLocStart().isMacroID() || S.inTemplateInstantiation()) return; + // Comparisons between two array types are ill-formed for operator<=>, so + // we shouldn't emit any additional warnings about it. + if (Opc == BO_Cmp && LHSType->isArrayType() && RHSType->isArrayType()) + return; + // For non-floating point types, check for self-comparisons of the form // x == x, x != x, x < x, etc. These always evaluate to a constant, and // often indicate logic errors in the program. @@ -9708,10 +9715,183 @@ static void diagnoseTautologicalComparison(Sema &S, SourceLocation Loc, } } +static ImplicitConversionKind castKindToImplicitConversionKind(CastKind CK) { + switch (CK) { + default: { +#ifndef NDEBUG + llvm::errs() << "unhandled cast kind: " << CastExpr::getCastKindName(CK) + << "\n"; +#endif + llvm_unreachable("unhandled cast kind"); + } + case CK_UserDefinedConversion: + return ICK_Identity; + case CK_LValueToRValue: + return ICK_Lvalue_To_Rvalue; + case CK_ArrayToPointerDecay: + return ICK_Array_To_Pointer; + case CK_FunctionToPointerDecay: + return ICK_Function_To_Pointer; + case CK_IntegralCast: + return ICK_Integral_Conversion; + case CK_FloatingCast: + return ICK_Floating_Conversion; + case CK_IntegralToFloating: + case CK_FloatingToIntegral: + return ICK_Floating_Integral; + case CK_IntegralComplexCast: + case CK_FloatingComplexCast: + case CK_FloatingComplexToIntegralComplex: + case CK_IntegralComplexToFloatingComplex: + return ICK_Complex_Conversion; + case CK_FloatingComplexToReal: + case CK_FloatingRealToComplex: + case CK_IntegralComplexToReal: + case CK_IntegralRealToComplex: + return ICK_Complex_Real; + } +} + +static bool checkThreeWayNarrowingConversion(Sema &S, QualType ToType, Expr *E, + QualType FromType, + SourceLocation Loc) { + // Check for a narrowing implicit conversion. + StandardConversionSequence SCS; + SCS.setToType(0, FromType); + SCS.setToType(1, ToType); + if (const auto *ICE = dyn_cast(E)) { + auto CastK = ICE->getCastKind(); + SCS.Second = castKindToImplicitConversionKind(CastK); + } + APValue PreNarrowingValue; + QualType PreNarrowingType; + switch (SCS.getNarrowingKind(S.Context, E, PreNarrowingValue, + PreNarrowingType, + /*IgnoreFloatToIntegralConversion*/ true)) { + case NK_Dependent_Narrowing: + // Implicit conversion to a narrower type, but the expression is + // value-dependent so we can't tell whether it's actually narrowing. + case NK_Not_Narrowing: + return false; + + case NK_Constant_Narrowing: + // Implicit conversion to a narrower type, and the value is not a constant + // expression. + S.Diag(E->getLocStart(), diag::err_spaceship_argument_narrowing) + << /*Constant*/ 1 + << PreNarrowingValue.getAsString(S.Context, PreNarrowingType) << ToType; + return true; + + case NK_Variable_Narrowing: + // Implicit conversion to a narrower type, and the value is not a constant + // expression. + case NK_Type_Narrowing: + S.Diag(E->getLocStart(), diag::err_spaceship_argument_narrowing) + << /*Constant*/ 0 << FromType << ToType; + // TODO: It's not a constant expression, but what if the user intended it + // to be? Can we produce notes to help them figure out why it isn't? + return true; + } + llvm_unreachable("unhandled case in switch"); +} + +static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S, + ExprResult &LHS, + ExprResult &RHS, + SourceLocation Loc) { + using CCT = ComparisonCategoryType; + + QualType LHSType = LHS.get()->getType(); + QualType RHSType = RHS.get()->getType(); + // Dig out the original argument type and expression before implicit casts + // were applied. These are the types/expressions we need to check the + // [expr.spaceship] requirements against. + ExprResult LHSStripped = LHS.get()->IgnoreParenImpCasts(); + ExprResult RHSStripped = RHS.get()->IgnoreParenImpCasts(); + QualType LHSStrippedType = LHSStripped.get()->getType(); + QualType RHSStrippedType = RHSStripped.get()->getType(); + + // C++2a [expr.spaceship]p3: If one of the operands is of type bool and the + // other is not, the program is ill-formed. + if (LHSStrippedType->isBooleanType() != RHSStrippedType->isBooleanType()) { + S.InvalidOperands(Loc, LHSStripped, RHSStripped); + return QualType(); + } + + int NumEnumArgs = (int)LHSStrippedType->isEnumeralType() + + RHSStrippedType->isEnumeralType(); + if (NumEnumArgs == 1) { + bool LHSIsEnum = LHSStrippedType->isEnumeralType(); + QualType OtherTy = LHSIsEnum ? RHSStrippedType : LHSStrippedType; + if (OtherTy->hasFloatingRepresentation()) { + S.InvalidOperands(Loc, LHSStripped, RHSStripped); + return QualType(); + } + } + if (NumEnumArgs == 2) { + // C++2a [expr.spaceship]p5: If both operands have the same enumeration + // type E, the operator yields the result of converting the operands + // to the underlying type of E and applying <=> to the converted operands. + if (!S.Context.hasSameUnqualifiedType(LHSStrippedType, RHSStrippedType)) { + S.InvalidOperands(Loc, LHSStripped, RHSStripped); + return QualType(); + } + QualType IntType = + LHSStrippedType->getAs()->getDecl()->getIntegerType(); + assert(IntType->isArithmeticType()); + + // We can't use `CK_IntegralCast` when the underlying type is 'bool', so we + // promote the boolean type, and all other promotable integer types, to + // avoid this. + if (IntType->isPromotableIntegerType()) + IntType = S.Context.getPromotedIntegerType(IntType); + + LHS = S.ImpCastExprToType(LHS.get(), IntType, CK_IntegralCast); + RHS = S.ImpCastExprToType(RHS.get(), IntType, CK_IntegralCast); + LHSType = RHSType = IntType; + } + + // C++2a [expr.spaceship]p4: If both operands have arithmetic types, the + // usual arithmetic conversions are applied to the operands. + QualType Type = S.UsualArithmeticConversions(LHS, RHS); + if (LHS.isInvalid() || RHS.isInvalid()) + return QualType(); + if (Type.isNull()) + return S.InvalidOperands(Loc, LHS, RHS); + assert(Type->isArithmeticType() || Type->isEnumeralType()); + + bool HasNarrowing = checkThreeWayNarrowingConversion( + S, Type, LHS.get(), LHSType, LHS.get()->getLocStart()); + HasNarrowing |= checkThreeWayNarrowingConversion( + S, Type, RHS.get(), RHSType, RHS.get()->getLocStart()); + if (HasNarrowing) + return QualType(); + + assert(!Type.isNull() && "composite type for <=> has not been set"); + + auto TypeKind = [&]() { + if (const ComplexType *CT = Type->getAs()) { + if (CT->getElementType()->hasFloatingRepresentation()) + return CCT::WeakEquality; + return CCT::StrongEquality; + } + if (Type->isIntegralOrEnumerationType()) + return CCT::StrongOrdering; + if (Type->hasFloatingRepresentation()) + return CCT::PartialOrdering; + llvm_unreachable("other types are unimplemented"); + }(); + + return S.CheckComparisonCategoryType(TypeKind, Loc); +} + static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS, ExprResult &RHS, SourceLocation Loc, BinaryOperatorKind Opc) { + if (Opc == BO_Cmp) + return checkArithmeticOrEnumeralThreeWayCompare(S, LHS, RHS, Loc); + // C99 6.5.8p3 / C99 6.5.9p4 QualType Type = S.UsualArithmeticConversions(LHS, RHS); if (LHS.isInvalid() || RHS.isInvalid()) @@ -9722,15 +9902,7 @@ static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS, checkEnumComparison(S, Loc, LHS.get(), RHS.get()); - enum { StrongEquality, PartialOrdering, StrongOrdering } Ordering; - if (Type->isAnyComplexType()) - Ordering = StrongEquality; - else if (Type->isFloatingType()) - Ordering = PartialOrdering; - else - Ordering = StrongOrdering; - - if (Ordering == StrongEquality && BinaryOperator::isRelationalOp(Opc)) + if (Type->isAnyComplexType() && BinaryOperator::isRelationalOp(Opc)) return S.InvalidOperands(Loc, LHS, RHS); // Check for comparisons of floating point operands using != and ==. @@ -9738,22 +9910,40 @@ static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS, S.CheckFloatComparison(Loc, LHS.get(), RHS.get()); // The result of comparisons is 'bool' in C++, 'int' in C. - // FIXME: For BO_Cmp, return the relevant comparison category type. return S.Context.getLogicalOperationType(); } // C99 6.5.8, C++ [expr.rel] QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, - SourceLocation Loc, BinaryOperatorKind Opc, - bool IsRelational) { - // Comparisons expect an rvalue, so convert to rvalue before any - // type-related checks. - LHS = DefaultFunctionArrayLvalueConversion(LHS.get()); - if (LHS.isInvalid()) - return QualType(); - RHS = DefaultFunctionArrayLvalueConversion(RHS.get()); - if (RHS.isInvalid()) - return QualType(); + SourceLocation Loc, + BinaryOperatorKind Opc) { + bool IsRelational = BinaryOperator::isRelationalOp(Opc); + bool IsThreeWay = Opc == BO_Cmp; + auto IsAnyPointerType = [](ExprResult E) { + QualType Ty = E.get()->getType(); + return Ty->isPointerType() || Ty->isMemberPointerType(); + }; + + // C++2a [expr.spaceship]p6: If at least one of the operands is of pointer + // type, array-to-pointer, ..., conversions are performed on both operands to + // bring them to their composite type. + // Otherwise, all comparisons expect an rvalue, so convert to rvalue before + // any type-related checks. + if (!IsThreeWay || IsAnyPointerType(LHS) || IsAnyPointerType(RHS)) { + LHS = DefaultFunctionArrayLvalueConversion(LHS.get()); + if (LHS.isInvalid()) + return QualType(); + RHS = DefaultFunctionArrayLvalueConversion(RHS.get()); + if (RHS.isInvalid()) + return QualType(); + } else { + LHS = DefaultLvalueConversion(LHS.get()); + if (LHS.isInvalid()) + return QualType(); + RHS = DefaultLvalueConversion(RHS.get()); + if (RHS.isInvalid()) + return QualType(); + } checkArithmeticNull(*this, LHS, RHS, Loc, /*isCompare=*/true); @@ -9771,8 +9961,6 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, (RHSType->isArithmeticType() || RHSType->isEnumeralType())) return checkArithmeticOrEnumeralCompare(*this, LHS, RHS, Loc, Opc); - QualType ResultTy = Context.getLogicalOperationType(); - const Expr::NullPointerConstantKind LHSNullKind = LHS.get()->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull); const Expr::NullPointerConstantKind RHSNullKind = @@ -9780,6 +9968,44 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, bool LHSIsNull = LHSNullKind != Expr::NPCK_NotNull; bool RHSIsNull = RHSNullKind != Expr::NPCK_NotNull; + auto computeResultTy = [&]() { + if (Opc != BO_Cmp) + return Context.getLogicalOperationType(); + assert(getLangOpts().CPlusPlus); + assert(Context.hasSameType(LHS.get()->getType(), RHS.get()->getType())); + + QualType CompositeTy = LHS.get()->getType(); + assert(!CompositeTy->isReferenceType()); + + auto buildResultTy = [&](ComparisonCategoryType Kind) { + return CheckComparisonCategoryType(Kind, Loc); + }; + + // C++2a [expr.spaceship]p7: If the composite pointer type is a function + // pointer type, a pointer-to-member type, or std::nullptr_t, the + // result is of type std::strong_equality + if (CompositeTy->isFunctionPointerType() || + CompositeTy->isMemberPointerType() || CompositeTy->isNullPtrType()) + // FIXME: consider making the function pointer case produce + // strong_ordering not strong_equality, per P0946R0-Jax18 discussion + // and direction polls + return buildResultTy(ComparisonCategoryType::StrongEquality); + + // C++2a [expr.spaceship]p8: If the composite pointer type is an object + // pointer type, p <=> q is of type std::strong_ordering. + if (CompositeTy->isPointerType()) { + // P0946R0: Comparisons between a null pointer constant and an object + // pointer result in std::strong_equality + if (LHSIsNull != RHSIsNull) + return buildResultTy(ComparisonCategoryType::StrongEquality); + return buildResultTy(ComparisonCategoryType::StrongOrdering); + } + // C++2a [expr.spaceship]p9: Otherwise, the program is ill-formed. + // TODO: Extend support for operator<=> to ObjC types. + return InvalidOperands(Loc, LHS, RHS); + }; + + if (!IsRelational && LHSIsNull != RHSIsNull) { bool IsEquality = Opc == BO_EQ; if (RHSIsNull) @@ -9807,29 +10033,30 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, // conformance with the C++ standard. diagnoseFunctionPointerToVoidComparison( *this, Loc, LHS, RHS, /*isError*/ (bool)isSFINAEContext()); - + if (isSFINAEContext()) 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() || - RHSType->isObjCObjectPointerType()))) { + (!LangOpts.ObjCAutoRefCount || !(LHSType->isObjCObjectPointerType() || + RHSType->isObjCObjectPointerType()))) { if (convertPointersToCompositeType(*this, Loc, LHS, RHS)) return QualType(); - else - return ResultTy; + return computeResultTy(); } } else if (LHSType->isPointerType() && RHSType->isPointerType()) { // C99 6.5.8p2 @@ -9880,7 +10107,7 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, else RHS = ImpCastExprToType(RHS.get(), LHSType, Kind); } - return ResultTy; + return computeResultTy(); } if (getLangOpts().CPlusPlus) { @@ -9890,11 +10117,11 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, if (!IsRelational && LHSIsNull && RHSIsNull) { if (LHSType->isNullPtrType()) { RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } if (RHSType->isNullPtrType()) { LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } } @@ -9903,12 +10130,12 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, 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 && @@ -9931,7 +10158,7 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer); else LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer); - return ResultTy; + return computeResultTy(); } } } @@ -9944,7 +10171,7 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, if (convertPointersToCompositeType(*this, Loc, LHS, RHS)) return QualType(); else - return ResultTy; + return computeResultTy(); } } @@ -9961,7 +10188,7 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, << RHS.get()->getSourceRange(); } RHS = ImpCastExprToType(RHS.get(), LHSType, CK_BitCast); - return ResultTy; + return computeResultTy(); } // Allow block pointers to be compared with null pointer constants. @@ -9985,7 +10212,7 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, RHS = ImpCastExprToType(RHS.get(), LHSType, LHSType->isPointerType() ? CK_BitCast : CK_AnyPointerToBlockPointerCast); - return ResultTy; + return computeResultTy(); } if (LHSType->isObjCObjectPointerType() || @@ -10018,7 +10245,7 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, RHS = ImpCastExprToType(E, LHSType, LPT ? CK_BitCast :CK_CPointerToObjCPointerCast); } - return ResultTy; + return computeResultTy(); } if (LHSType->isObjCObjectPointerType() && RHSType->isObjCObjectPointerType()) { @@ -10032,20 +10259,20 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, 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()) || @@ -10085,30 +10312,30 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, 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(); } } @@ -11761,19 +11988,17 @@ ExprResult Sema::CreateBuiltinBinOp(SourceLocation OpLoc, case BO_GE: case BO_GT: ConvertHalfVec = true; - ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc, true); + ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc); break; case BO_EQ: case BO_NE: ConvertHalfVec = true; - ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc, false); + ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc); break; case BO_Cmp: - // FIXME: Implement proper semantic checking of '<=>'. ConvertHalfVec = true; - ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc, true); - if (!ResultTy.isNull()) - ResultTy = Context.VoidTy; + ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc); + assert(ResultTy.isNull() || ResultTy->getAsCXXRecordDecl()); break; case BO_And: checkObjCPointerIntrospection(*this, LHS, RHS, OpLoc); diff --git a/lib/Sema/SemaOverload.cpp b/lib/Sema/SemaOverload.cpp index f534589c50..362fe71f3c 100644 --- a/lib/Sema/SemaOverload.cpp +++ b/lib/Sema/SemaOverload.cpp @@ -288,11 +288,11 @@ static const Expr *IgnoreNarrowingConversion(const Expr *Converted) { /// 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 @@ StandardConversionSequence::getNarrowingKind(ASTContext &Ctx, 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"); @@ -7988,7 +7990,8 @@ public: // bool operator>=(T, T); // bool operator==(T, T); // bool operator!=(T, T); - void addRelationalPointerOrEnumeralOverloads() { + // R operator<=>(T, T) + void addGenericBinaryPointerOrEnumeralOverloads() { // C++ [over.match.oper]p3: // [...]the built-in candidates include all of the candidate operator // functions defined in 13.6 that, compared to the given operator, [...] @@ -8061,7 +8064,6 @@ public: UserDefinedBinaryOperators.count(std::make_pair(CanonType, CanonType))) continue; - QualType ParamTypes[2] = { *Enum, *Enum }; S.AddBuiltinCandidate(ParamTypes, Args, CandidateSet); } @@ -8179,6 +8181,41 @@ public: } } + // C++2a [over.built]p14: + // + // For every integral type T there exists a candidate operator function + // of the form + // + // std::strong_ordering operator<=>(T, T) + // + // C++2a [over.built]p15: + // + // For every pair of floating-point types L and R, there exists a candidate + // operator function of the form + // + // std::partial_ordering operator<=>(L, R); + // + // FIXME: The current specification for integral types doesn't play nice with + // the direction of p0946r0, which allows mixed integral and unscoped-enum + // comparisons. Under the current spec this can lead to ambiguity during + // overload resolution. For example: + // + // enum A : int {a}; + // auto x = (a <=> (long)42); + // + // error: call is ambiguous for arguments 'A' and 'long'. + // note: candidate operator<=>(int, int) + // note: candidate operator<=>(long, long) + // + // To avoid this error, this function deviates from the specification and adds + // the mixed overloads `operator<=>(L, R)` where L and R are promoted + // arithmetic types (the same as the generic relational overloads). + // + // For now this function acts as a placeholder. + void addThreeWayArithmeticOverloads() { + addGenericBinaryArithmeticOverloads(); + } + // C++ [over.built]p17: // // For every pair of promoted integral types L and R, there @@ -8747,12 +8784,14 @@ void Sema::AddBuiltinOperatorCandidates(OverloadedOperatorKind Op, case OO_Greater: case OO_LessEqual: case OO_GreaterEqual: - OpBuilder.addRelationalPointerOrEnumeralOverloads(); + OpBuilder.addGenericBinaryPointerOrEnumeralOverloads(); OpBuilder.addGenericBinaryArithmeticOverloads(); break; case OO_Spaceship: - llvm_unreachable("<=> expressions not supported yet"); + OpBuilder.addGenericBinaryPointerOrEnumeralOverloads(); + OpBuilder.addThreeWayArithmeticOverloads(); + break; case OO_Percent: case OO_Caret: diff --git a/test/CodeGenCXX/Inputs/std-compare.h b/test/CodeGenCXX/Inputs/std-compare.h new file mode 100644 index 0000000000..a6ab605bb5 --- /dev/null +++ b/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 diff --git a/test/CodeGenCXX/cxx2a-compare.cpp b/test/CodeGenCXX/cxx2a-compare.cpp new file mode 100644 index 0000000000..64dd60ef3d --- /dev/null +++ b/test/CodeGenCXX/cxx2a-compare.cpp @@ -0,0 +1,186 @@ +// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | \ +// RUN: FileCheck %s \ +// RUN: '-DWE="class.std::__1::weak_equality"' \ +// RUN: '-DSO="class.std::__1::strong_ordering"' \ +// RUN: '-DSE="class.std::__1::strong_equality"' \ +// RUN: '-DPO="class.std::__1::partial_ordering"' \ +// RUN: -DEQ=0 -DLT=-1 -DGT=1 -DUNORD=-127 -DNE=1 + +#include "Inputs/std-compare.h" + +// Ensure we don't emit definitions for the global variables +// since the builtins shouldn't ODR use them. +// CHECK-NOT: constant %[[SO]] +// CHECK-NOT: constant %[[SE]] +// CHECK-NOT: constant %[[WE]] +// CHECK-NOT: constant %[[PO]] + +// 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, i8 [[LT]], i8 [[GT]] + // CHECK: %cmp.eq = icmp eq i32 %0, %1 + // CHECK: %sel.eq = select i1 %cmp.eq, i8 [[EQ]], i8 %sel.lt + // CHECK: %__value_ = getelementptr inbounds %[[SO]], %[[SO]]* %retval, i32 0, i32 0 + // CHECK: store i8 %sel.eq, i8* %__value_, align 1 + // CHECK: %[[FINAL:.*]] = getelementptr inbounds %[[SO]], %[[SO]]* %retval + // CHECK: %[[RET:.*]] = load i8, i8* %[[FINAL]] + // CHECK: ret i8 %[[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, i8 [[LT]], i8 [[GT]] + // CHECK: %cmp.eq = icmp eq i32 %0, %1 + // CHECK: %sel.eq = select i1 %cmp.eq, i8 [[EQ]], i8 %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, i8 [[EQ]], i8 [[UNORD]] + // CHECK: %cmp.gt = fcmp ogt double %0, %1 + // CHECK: %sel.gt = select i1 %cmp.gt, i8 [[GT]], i8 %sel.eq + // CHECK: %cmp.lt = fcmp olt double %0, %1 + // CHECK: %sel.lt = select i1 %cmp.lt, i8 [[LT]], i8 %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, i8 [[LT]], i8 [[GT]] + // CHECK: %cmp.eq = icmp eq i32* %0, %1 + // CHECK: %sel.eq = select i1 %cmp.eq, i8 [[EQ]], i8 %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, i8 [[EQ]], i8 [[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]], i8 [[EQ]], i8 [[NE]] + // CHECK: %retval + // CHECK: %sel.eq + return x <=> y; +} + +// CHECK-LABEL: @_Z13test_constantv +auto test_constant() { + // CHECK: entry: + // CHECK-NOT: icmp + // CHECK: %__value_ = getelementptr inbounds %[[SO]], %[[SO]]* %retval + // CHECK-NEXT: store i8 [[LT]], i8* %__value_ + // CHECK-NEXT: %[[TMP:.*]] = getelementptr inbounds %[[SO]], %[[SO]]* %retval + // CHECK-NEXT: %[[RET:.*]] = load i8, i8* %[[TMP]] + // CHECK-NEXT: ret i8 %[[RET]] + const int x = 42; + const int y = 101; + return x <=> y; +} + +// CHECK-LABEL: @_Z16test_nullptr_objPiDn +auto test_nullptr_obj(int* x, decltype(nullptr) y) { + // CHECK: %retval = alloca %[[SE]] + // CHECK: %cmp.eq = icmp eq i32* %0, null + // CHECK: %sel.eq = select i1 %cmp.eq, i8 [[EQ]], i8 [[NE]] + // CHECK: %retval + // CHECK: %sel.eq + return x <=> y; +} + +// CHECK-LABEL: @_Z18unscoped_enum_testijlm +void unscoped_enum_test(int i, unsigned u, long l, unsigned long ul) { + enum EnumA : int { A }; + enum EnumB : unsigned { B }; + // CHECK: %[[I:.*]] = load {{.*}} %i.addr + // CHECK: icmp slt i32 {{.*}} %[[I]] + (void)(A <=> i); + + // CHECK: %[[U:.*]] = load {{.*}} %u.addr + // CHECK: icmp ult i32 {{.*}} %[[U]] + (void)(A <=> u); + + // CHECK: %[[L:.*]] = load {{.*}} %l.addr + // CHECK: icmp slt i64 {{.*}} %[[L]] + (void)(A <=> l); + + // CHECK: %[[U2:.*]] = load {{.*}} %u.addr + // CHECK: icmp ult i32 {{.*}} %[[U2]] + (void)(B <=> u); + + // CHECK: %[[UL:.*]] = load {{.*}} %ul.addr + // CHECK: icmp ult i64 {{.*}} %[[UL]] + (void)(B <=> ul); +} + +namespace NullptrTest { +using nullptr_t = decltype(nullptr); + +// CHECK-LABEL: @_ZN11NullptrTest4testEDnDn( +auto test(nullptr_t x, nullptr_t y) { + // CHECK-NOT: select + // CHECK: %__value_ = getelementptr inbounds %[[SE]], %[[SE]]* %retval + // CHECK-NEXT: store i8 [[EQ]], i8* %__value_ + // CHECK: ret + return x <=> y; +} +} // namespace NullptrTest + +namespace ComplexTest { + +auto test_float(_Complex float x, _Complex float y) { + // CHECK: %cmp.eq.r = fcmp oeq float %x.real, %y.real + // CHECK: %cmp.eq.i = fcmp oeq float %x.imag, %y.imag + // CHECK: %and.eq = and i1 %cmp.eq.r, %cmp.eq.i + // CHECK: %sel.eq = select i1 %and.eq, i8 [[EQ]], i8 [[NE]] + // CHECK: %__value_ = getelementptr inbounds %[[WE]], %[[WE]]* %retval + // CHECK: store i8 %sel.eq, i8* %__value_, align 1 + return x <=> y; +} + +// CHECK-LABEL: @_ZN11ComplexTest8test_intECiS0_( +auto test_int(_Complex int x, _Complex int y) { + // CHECK: %cmp.eq.r = icmp eq i32 %x.real, %y.real + // CHECK: %cmp.eq.i = icmp eq i32 %x.imag, %y.imag + // CHECK: %and.eq = and i1 %cmp.eq.r, %cmp.eq.i + // CHECK: %sel.eq = select i1 %and.eq, i8 [[EQ]], i8 [[NE]] + // CHECK: %__value_ = getelementptr inbounds %[[SE]], %[[SE]]* %retval + // CHECK: store i8 %sel.eq, i8* %__value_, align 1 + return x <=> y; +} + +} // namespace ComplexTest diff --git a/test/PCH/Inputs/std-compare.h b/test/PCH/Inputs/std-compare.h new file mode 100644 index 0000000000..a6ab605bb5 --- /dev/null +++ b/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 diff --git a/test/PCH/cxx2a-compare.cpp b/test/PCH/cxx2a-compare.cpp new file mode 100644 index 0000000000..9ad368d88f --- /dev/null +++ b/test/PCH/cxx2a-compare.cpp @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -pedantic-errors -std=c++2a -emit-pch %s -o %t +// RUN: %clang_cc1 -pedantic-errors -std=c++2a -include-pch %t -verify %s +// RUN: %clang_cc1 -pedantic-errors -std=c++2a -include-pch %t -emit-llvm %s -o - + + +#ifndef HEADER +#define HEADER + +#include "Inputs/std-compare.h" +constexpr auto foo() { + return (42 <=> 101); +} + +inline auto bar(int x) { + return (1 <=> x); +} + +#else + +// expected-no-diagnostics + +static_assert(foo() < 0); + +auto bar2(int x) { + return bar(x); +} + +#endif diff --git a/test/SemaCXX/Inputs/std-compare.h b/test/SemaCXX/Inputs/std-compare.h new file mode 100644 index 0000000000..a6ab605bb5 --- /dev/null +++ b/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 diff --git a/test/SemaCXX/compare-cxx2a.cpp b/test/SemaCXX/compare-cxx2a.cpp index d88e3bae41..43da6983d7 100644 --- a/test/SemaCXX/compare-cxx2a.cpp +++ b/test/SemaCXX/compare-cxx2a.cpp @@ -1,38 +1,43 @@ // 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; - int b[3], c[3]; + int *b = nullptr; + (void)(a <=> a); // expected-warning {{self-comparison always evaluates to 'std::strong_ordering::equal'}} (void)(b <=> b); // expected-warning {{self-comparison always evaluates to 'std::strong_ordering::equal'}} - (void)(b <=> c); // expected-warning {{array comparison always evaluates to a constant}} } void test0(long a, unsigned long b) { - enum EnumA {A}; + enum EnumA : int {A}; enum EnumB {B}; enum EnumC {C = 0x10000}; - // (a,b) - // FIXME: <=> should never produce -Wsign-compare warnings. All the possible error - // cases involve narrowing conversions and so are ill-formed. - (void)(a <=> (unsigned long) b); // expected-warning {{comparison of integers of different signs}} + (void)((short)a <=> (unsigned short)b); + + // (a,b) + (void)(a <=> (unsigned long)b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} (void)(a <=> (unsigned int) b); (void)(a <=> (unsigned short) b); (void)(a <=> (unsigned char) b); - (void)((long) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((int) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((short) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((signed char) a <=> b); // expected-warning {{comparison of integers of different signs}} - (void)((long) a <=> (unsigned long) b); // expected-warning {{comparison of integers of different signs}} - (void)((int) a <=> (unsigned int) b); // expected-warning {{comparison of integers of different signs}} + (void)((long)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((short)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((signed char)a <=> b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((long)a <=> (unsigned long)b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)a <=> (unsigned int)b); // expected-error {{argument to 'operator<=>' cannot be narrowed}} (void)((short) a <=> (unsigned short) b); (void)((signed char) a <=> (unsigned char) b); -#if 0 + (void)(A < 42); // (A,b) (void)(A <=> (unsigned long) b); (void)(A <=> (unsigned int) b); @@ -48,7 +53,7 @@ void test0(long a, unsigned long b) { (void)((signed char) A <=> (unsigned char) b); // (a,B) - (void)(a <=> (unsigned long) B); + (void)(a <=> (unsigned long) B); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} (void)(a <=> (unsigned int) B); (void)(a <=> (unsigned short) B); (void)(a <=> (unsigned char) B); @@ -56,8 +61,8 @@ void test0(long a, unsigned long b) { (void)((int) a <=> B); (void)((short) a <=> B); (void)((signed char) a <=> B); - (void)((long) a <=> (unsigned long) B); - (void)((int) a <=> (unsigned int) B); + (void)((long) a <=> (unsigned long) B); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} + (void)((int) a <=> (unsigned int) B); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'}} (void)((short) a <=> (unsigned short) B); (void)((signed char) a <=> (unsigned char) B); @@ -76,7 +81,7 @@ void test0(long a, unsigned long b) { (void)((signed char) C <=> (unsigned char) b); // (a,C) - (void)(a <=> (unsigned long) C); + (void)(a <=> (unsigned long) C); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} (void)(a <=> (unsigned int) C); (void)(a <=> (unsigned short) C); (void)(a <=> (unsigned char) C); @@ -84,11 +89,10 @@ void test0(long a, unsigned long b) { (void)((int) a <=> C); (void)((short) a <=> C); // expected-warning {{comparison of constant 'C' (65536) with expression of type 'short' is always 'std::strong_ordering::less'}} (void)((signed char) a <=> C); // expected-warning {{comparison of constant 'C' (65536) with expression of type 'signed char' is always 'std::strong_ordering::less'}} - (void)((long) a <=> (unsigned long) C); - (void)((int) a <=> (unsigned int) C); + (void)((long) a <=> (unsigned long) C); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'long' to 'unsigned long'}} + (void)((int) a <=> (unsigned int) C); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'}} (void)((short) a <=> (unsigned short) C); (void)((signed char) a <=> (unsigned char) C); -#endif // (0x80000,b) (void)(0x80000 <=> (unsigned long) b); @@ -105,7 +109,7 @@ void test0(long a, unsigned long b) { (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 +117,23 @@ void test0(long a, unsigned long b) { (void)((int) a <=> 0x80000); (void)((short) a <=> 0x80000); // expected-warning {{comparison of constant 524288 with expression of type 'short' is always 'std::strong_ordering::less'}} (void)((signed char) a <=> 0x80000); // expected-warning {{comparison of constant 524288 with expression of type 'signed char' is always 'std::strong_ordering::less'}} - (void)((long) a <=> (unsigned long) 0x80000); // expected-warning {{comparison of integers of different signs}} - (void)((int) a <=> (unsigned int) 0x80000); // expected-warning {{comparison of integers of different signs}} + (void)((long)a <=> (unsigned long)0x80000); // expected-error {{argument to 'operator<=>' cannot be narrowed}} + (void)((int)a <=> (unsigned int)0x80000); // expected-error {{argument to 'operator<=>' cannot be narrowed}} (void)((short) a <=> (unsigned short) 0x80000); (void)((signed char) a <=> (unsigned char) 0x80000); } -void test5(bool b) { - (void) (b <=> -10); // expected-warning{{comparison of constant -10 with expression of type 'bool' is always 'std::strong_ordering::greater'}} - (void) (b <=> -1); // expected-warning{{comparison of constant -1 with expression of type 'bool' is always 'std::strong_ordering::greater'}} - (void) (b <=> 0); - (void) (b <=> 1); - (void) (b <=> 2); // expected-warning{{comparison of constant 2 with expression of type 'bool' is always 'std::strong_ordering::less'}} - (void) (b <=> 10); // expected-warning{{comparison of constant 10 with expression of type 'bool' is always 'std::strong_ordering::less'}} +void test5(bool b, bool b2) { + enum EnumA { A }; + (void)(b <=> b2); // OK + (void)(true <=> b); // OK + (void)(b <=> -10); // expected-error {{invalid operands to binary expression ('bool' and 'int')}} + (void)(b <=> char(1)); // expected-error {{invalid operands to binary expression ('bool' and 'char')}} + (void)(b <=> A); // expected-error {{invalid operands to binary expression ('bool' and 'EnumA')}} + + // FIXME: Should this be accepted when narrowing doesn't occur? + (void)(b <=> 0); // expected-error {{invalid operands to binary expression ('bool' and 'int')}} + (void)(b <=> 1); // expected-error {{invalid operands to binary expression ('bool' and 'int')}} } void test6(signed char sc) { @@ -142,13 +150,13 @@ void test7(unsigned long other) { (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 +180,245 @@ void test7(unsigned long other) { (void)((unsigned char)other <=> (unsigned short)(0x100)); // expected-warning{{less}} (void)((unsigned short)other <=> (unsigned char)(0xff)); } + +void test8(void *vp, const void *cvp, int *ip) { + (void)(vp <=> cvp); // OK, void* comparisons are allowed. + (void)(vp <=> ip); + (void)(ip <=> cvp); +} + +void test9(long double ld, double d, float f, int i, long long ll) { + (void)(f <=> ll); // OK, floating-point to integer is OK + (void)(d <=> ld); + (void)(i <=> f); +} + +typedef int *INTPTR; +void test_typedef_bug(int *x, INTPTR y) { + (void)(x <=> y); +} + +using nullptr_t = decltype(nullptr); + +struct Class {}; +struct ClassB : Class {}; +struct Class2 {}; +using FnTy = void(int); +using FnTy2 = long(int); +using MemFnTy = void (Class::*)() const; +using MemFnTyB = void (ClassB::*)() const; +using MemFnTy2 = void (Class::*)(); +using MemFnTy3 = void (Class2::*)() const; +using MemDataTy = long(Class::*); + +void test_nullptr(int *x, FnTy *fp, MemFnTy memp, MemDataTy memdp) { + auto r1 = (nullptr <=> nullptr); + ASSERT_EXPR_TYPE(r1, std::strong_equality); + + auto r2 = (nullptr <=> x); + ASSERT_EXPR_TYPE(r2, std::strong_equality); + + auto r3 = (fp <=> nullptr); + ASSERT_EXPR_TYPE(r3, std::strong_equality); + + auto r4 = (0 <=> fp); + ASSERT_EXPR_TYPE(r4, std::strong_equality); + + auto r5 = (nullptr <=> memp); + ASSERT_EXPR_TYPE(r5, std::strong_equality); + + auto r6 = (0 <=> memdp); + ASSERT_EXPR_TYPE(r6, std::strong_equality); + + auto r7 = (0 <=> nullptr); + ASSERT_EXPR_TYPE(r7, std::strong_equality); +} + +void test_compatible_pointer(FnTy *f1, FnTy2 *f2, MemFnTy mf1, MemFnTyB mfb, + MemFnTy2 mf2, MemFnTy3 mf3) { + (void)(f1 <=> f2); // expected-error {{distinct pointer types}} + + auto r1 = (mf1 <=> mfb); // OK + ASSERT_EXPR_TYPE(r1, std::strong_equality); + ASSERT_EXPR_TYPE((mf1 <=> mfb), std::strong_equality); + + (void)(mf1 <=> mf2); // expected-error {{distinct pointer types}} + (void)(mf3 <=> mf1); // expected-error {{distinct pointer types}} +} + +// Test that variable narrowing is deferred for value dependent expressions +template +auto test_template_overflow() { + // expected-error@+1 {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned long'}} + return (Val <=> (unsigned long)0); +} +template auto test_template_overflow<0>(); +template auto test_template_overflow<-1>(); // expected-note {{requested here}} + +void test_enum_integral_compare() { + enum EnumA : int {A, ANeg = -1, AMax = __INT_MAX__}; + enum EnumB : unsigned {B, BMax = __UINT32_MAX__ }; + enum EnumC : int {C = -1, C0 = 0}; + + (void)(A <=> C); // expected-error {{invalid operands to binary expression ('EnumA' and 'EnumC')}} + + (void)(A <=> (unsigned)0); + (void)((unsigned)0 <=> A); + (void)(ANeg <=> (unsigned)0); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)((unsigned)0 <=> ANeg); // expected-error {{cannot be narrowed}} + + (void)(B <=> 42); + (void)(42 <=> B); + (void)(B <=> (unsigned long long)42); + (void)(B <=> -1); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)(BMax <=> (unsigned long)-1); + + (void)(C0 <=> (unsigned)42); + (void)(C <=> (unsigned)42); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} +} + +namespace EnumCompareTests { + +enum class EnumA { A, A2 }; +enum class EnumB { B }; +enum class EnumC : unsigned { C }; + +void test_enum_enum_compare_no_builtin() { + auto r1 = (EnumA::A <=> EnumA::A2); // OK + ASSERT_EXPR_TYPE(r1, std::strong_ordering); + (void)(EnumA::A <=> EnumA::A); // expected-warning {{self-comparison always evaluates to 'std::strong_ordering::equal'}} + (void)(EnumA::A <=> EnumB::B); // expected-error {{invalid operands to binary expression ('EnumCompareTests::EnumA' and 'EnumCompareTests::EnumB')}} + (void)(EnumB::B <=> EnumA::A); // expected-error {{invalid operands}} +} + +template +struct Tag {}; +// expected-note@+1 {{candidate}} +Tag<0> operator<=>(EnumA, EnumA) { + return {}; +} +Tag<1> operator<=>(EnumA, EnumB) { + return {}; +} + +void test_enum_ovl_provided() { + auto r1 = (EnumA::A <=> EnumA::A); + ASSERT_EXPR_TYPE(r1, Tag<0>); + auto r2 = (EnumA::A <=> EnumB::B); + ASSERT_EXPR_TYPE(r2, Tag<1>); + (void)(EnumB::B <=> EnumA::A); // expected-error {{invalid operands to binary expression ('EnumCompareTests::EnumB' and 'EnumCompareTests::EnumA')}} +} + +void enum_float_test() { + enum EnumA { A }; + (void)(A <=> (float)0); // expected-error {{invalid operands to binary expression ('EnumA' and 'float')}} + (void)((double)0 <=> A); // expected-error {{invalid operands to binary expression ('double' and 'EnumA')}} + (void)((long double)0 <=> A); // expected-error {{invalid operands to binary expression ('long double' and 'EnumA')}} +} + +enum class Bool1 : bool { Zero, + One }; +enum Bool2 : bool { B2_Zero, + B2_One }; + +void test_bool_enum(Bool1 A1, Bool1 A2, Bool2 B1, Bool2 B2) { + (void)(A1 <=> A2); + (void)(B1 <=> B2); +} + +} // namespace EnumCompareTests + +namespace TestUserDefinedConvSeq { + +template +struct Conv { + constexpr operator T() const { return Val; } + operator T() { return Val; } +}; + +void test_user_conv() { + { + using C = Conv; + C c; + const C cc; + (void)(0 <=> c); + (void)(c <=> -1); + (void)((unsigned)0 <=> cc); + (void)((unsigned)0 <=> c); // expected-error {{argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'}} + } + { + using C = Conv; + C c; + const C cc; + (void)(c <=> 0); + (void)(cc <=> (unsigned)0); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)(c <=> (unsigned)0); // expected-error {{cannot be narrowed from type 'int' to 'unsigned int'}} + } +} + +} // namespace TestUserDefinedConvSeq + +void test_array_conv() { + int arr[5]; + int *ap = arr + 2; + int arr2[3]; + (void)(arr <=> arr); // expected-error {{invalid operands to binary expression ('int [5]' and 'int [5]')}} + (void)(+arr <=> arr); +} + +void test_mixed_float_int(float f, double d, long double ld) { + extern int i; + extern unsigned u; + extern long l; + extern short s; + extern unsigned short us; + auto r1 = (f <=> i); + ASSERT_EXPR_TYPE(r1, std::partial_ordering); + + auto r2 = (us <=> ld); + ASSERT_EXPR_TYPE(r2, std::partial_ordering); + + auto r3 = (s <=> f); + ASSERT_EXPR_TYPE(r3, std::partial_ordering); + + auto r4 = (0.0 <=> i); + ASSERT_EXPR_TYPE(r4, std::partial_ordering); +} + +namespace NullptrTest { +using nullptr_t = decltype(nullptr); +void foo(nullptr_t x, nullptr_t y) { + auto r = x <=> y; + ASSERT_EXPR_TYPE(r, std::strong_equality); +} +} // namespace NullptrTest + +namespace ComplexTest { + +enum class StrongE {}; +enum WeakE { E_One, + E_Two }; + +void test_diag(_Complex int ci, _Complex float cf, _Complex double cd, int i, float f, StrongE E1, WeakE E2, int *p) { + (void)(ci <=> (_Complex int &)ci); + (void)(ci <=> cf); + (void)(ci <=> i); + (void)(ci <=> f); + (void)(cf <=> i); + (void)(cf <=> f); + (void)(ci <=> p); // expected-error {{invalid operands}} + (void)(ci <=> E1); // expected-error {{invalid operands}} + (void)(E2 <=> cf); // expected-error {{invalid operands}} +} + +void test_int(_Complex int x, _Complex int y) { + auto r = x <=> y; + ASSERT_EXPR_TYPE(r, std::strong_equality); +} + +void test_double(_Complex double x, _Complex double y) { + auto r = x <=> y; + ASSERT_EXPR_TYPE(r, std::weak_equality); +} + +} // namespace ComplexTest diff --git a/test/SemaCXX/constant-expression-cxx2a.cpp b/test/SemaCXX/constant-expression-cxx2a.cpp index 935cbefc7c..18c4a726fd 100644 --- a/test/SemaCXX/constant-expression-cxx2a.cpp +++ b/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,194 @@ namespace ThreeWayComparison { 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() { + void(1 <=> 1); + } + + 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; - constexpr void f() { // expected-error {{constant expression}} - void(1 <=> 1); // expected-note {{constant expression}} + MemPtrT P1 = &MemPtr::foo; + MemPtrT P12 = &MemPtr::foo; + MemPtrT P2 = &MemPtr::bar; + MemPtrT P3 = nullptr; + + auto eq = (P1 <=> P12); + CHECK_TYPE(decltype(eq), SE); + CHECK(eq.test_eq(EQ)); + + auto neq = (P1 <=> P2); + CHECK_TYPE(decltype(eq), SE); + CHECK(neq.test_eq(NEQ)); + + auto eq2 = (P3 <=> nullptr); + CHECK_TYPE(decltype(eq2), SE); + CHECK(eq2.test_eq(EQ)); + } + { + using SE = std::strong_equality; + auto EQ = SE::equal; + auto NEQ = SE::nonequal; + + FnPtrT F1 = &FnPtr1; + FnPtrT F12 = &FnPtr1; + FnPtrT F2 = &FnPtr2; + FnPtrT F3 = nullptr; + + auto eq = (F1 <=> F12); + CHECK_TYPE(decltype(eq), SE); + CHECK(eq.test_eq(EQ)); + + auto neq = (F1 <=> F2); + CHECK_TYPE(decltype(neq), SE); + CHECK(neq.test_eq(NEQ)); + } + { // mixed nullptr tests + using SO = std::strong_ordering; + using SE = std::strong_equality; + + int x = 42; + int *xp = &x; + + MemPtrT mf = nullptr; + MemPtrT mf2 = &MemPtr::foo; + auto r3 = (mf <=> nullptr); + CHECK_TYPE(decltype(r3), std::strong_equality); + CHECK(r3.test_eq(SE::equal)); } - // TODO: defaulted operator <=> + return true; +}(); + +template +constexpr bool test_constexpr() { + using nullptr_t = decltype(nullptr); + using LHSTy = decltype(LHS); + using RHSTy = decltype(RHS); + // expected-note@+1 {{subexpression not valid in a constant expression}} + auto Res = (LHS <=> RHS); + if constexpr (__is_same(LHSTy, nullptr_t) || __is_same(RHSTy, nullptr_t)) { + CHECK_TYPE(decltype(Res), std::strong_equality); + } + if (ExpectTrue) + return Res == 0; + return Res != 0; +} +int dummy = 42; +int dummy2 = 101; + +constexpr bool tc1 = test_constexpr(); +constexpr bool tc2 = test_constexpr<&dummy, nullptr>(); + +// OK, equality comparison only +constexpr bool tc3 = test_constexpr<&MemPtr::foo, nullptr>(); +constexpr bool tc4 = test_constexpr(); +constexpr bool tc5 = test_constexpr<&MemPtr::foo, &MemPtr::bar>(); + +constexpr bool tc6 = test_constexpr<&MemPtr::data, nullptr>(); +constexpr bool tc7 = test_constexpr(); +constexpr bool tc8 = test_constexpr<&MemPtr::data, &MemPtr::data2>(); + +// expected-error@+1 {{must be initialized by a constant expression}} +constexpr bool tc9 = test_constexpr<&dummy, &dummy2>(); // expected-note {{in call}} + +template +constexpr T makeComplex(R r, I i) { + T res{r, i}; + return res; +}; + +template +constexpr bool complex_test(T x, T y, ResultT Expect) { + auto res = x <=> y; + CHECK_TYPE(decltype(res), ResultT); + return res.test_eq(Expect); } +static_assert(complex_test(makeComplex<_Complex double>(0.0, 0.0), + makeComplex<_Complex double>(0.0, 0.0), + std::weak_equality::equivalent)); +static_assert(complex_test(makeComplex<_Complex double>(0.0, 0.0), + makeComplex<_Complex double>(1.0, 0.0), + std::weak_equality::nonequivalent)); +static_assert(complex_test(makeComplex<_Complex double>(0.0, 0.0), + makeComplex<_Complex double>(0.0, 1.0), + std::weak_equality::nonequivalent)); +static_assert(complex_test(makeComplex<_Complex int>(0, 0), + makeComplex<_Complex int>(0, 0), + std::strong_equality::equal)); +static_assert(complex_test(makeComplex<_Complex int>(0, 0), + makeComplex<_Complex int>(1, 0), + std::strong_equality::nonequal)); +// TODO: defaulted operator <=> +} // namespace ThreeWayComparison diff --git a/test/SemaCXX/std-compare-cxx2a.cpp b/test/SemaCXX/std-compare-cxx2a.cpp new file mode 100644 index 0000000000..3aa625a272 --- /dev/null +++ b/test/SemaCXX/std-compare-cxx2a.cpp @@ -0,0 +1,65 @@ +// Test diagnostics for ill-formed STL headers. + +// RUN: %clang_cc1 -triple x86_64-apple-darwin -fcxx-exceptions -fsyntax-only -pedantic -verify -Wsign-compare -std=c++2a %s + +void compare_not_found_test() { + // expected-error@+1 {{cannot deduce return type of 'operator<=>' because type partial_ordering was not found; include }} + (void)(0.0 <=> 42.123); +} + +namespace std { +inline namespace __1 { +struct partial_ordering; // expected-note {{forward declaration}} +} +} // namespace std + +auto compare_incomplete_test() { + // expected-error@+1 {{incomplete type 'std::partial_ordering' where a complete type is required}} + return (-1.2 <=> 123.0); +} + +namespace std { +inline namespace __1 { +struct partial_ordering { + unsigned value; +}; +} // namespace __1 +} // namespace std + +auto missing_member_test() { + // expected-error@+1 {{standard library implementation of 'std::partial_ordering' is not supported; member 'equivalent' is missing}} + return (1.0 <=> 1.0); +} + +namespace std { +inline namespace __1 { +struct strong_ordering { + long long value; + static const strong_ordering equivalent; // expected-note {{declared here}} +}; +} // namespace __1 +} // namespace std + +auto test_non_constexpr_var() { + // expected-error@+1 {{standard library implementation of 'std::strong_ordering' is not supported; member 'equivalent' does not have expected form}} + return (1 <=> 0); +} + +namespace std { +inline namespace __1 { +struct strong_equality { + char value = 0; + constexpr strong_equality() = default; + // non-trivial + constexpr strong_equality(strong_equality const &other) : value(other.value) {} +}; +} // namespace __1 +} // namespace std + +struct Class {}; +using MemPtr = void (Class::*)(int); + +auto test_non_trivial(MemPtr LHS, MemPtr RHS) { + // expected-error@+1 {{standard library implementation of 'std::strong_equality' is not supported; the type is not trivially copyable}} + return LHS <=> RHS; +}