[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

llvm-svn: 331677
This commit is contained in:
Eric Fiselier 2018-05-07 21:07:10 +00:00
parent f53d9abd7e
commit 0683c0e68d
24 changed files with 3545 additions and 389 deletions

View File

@ -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;

View File

@ -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 <array>
#include <cassert>
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<unsigned>(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<ComparisonCategoryResult>
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<ComparisonCategoryInfo *>(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<char, ComparisonCategoryInfo> Data;
mutable NamespaceDecl *StdNS = nullptr;
};
} // namespace clang
#endif

View File

@ -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<Expr>(Op); }
const Expr *getSubExpr() const { return cast<Expr>(Op); }

View File

@ -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 <compare>">;
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.

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -20,6 +20,7 @@ add_clang_library(clangAST
CommentLexer.cpp
CommentParser.cpp
CommentSema.cpp
ComparisonCategories.cpp
DataCollection.cpp
Decl.cpp
DeclarationName.cpp

View File

@ -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<VarDecl>(Lookup.front()))
return nullptr;
Objects.emplace_back(ValueKind, cast<VarDecl>(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<NamespaceDecl>(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<CXXRecordDecl>(Lookup.front()))
return RD;
return nullptr;
}
const ComparisonCategoryInfo *
ComparisonCategories::lookupInfo(ComparisonCategoryType Kind) const {
auto It = Data.find(static_cast<char>(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<unsigned>(CCT::First),
End = static_cast<unsigned>(CCT::Last);
I <= End; ++I) {
CCT Kind = static_cast<CCT>(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<ComparisonCategoryResult>
ComparisonCategories::getPossibleResultsForType(ComparisonCategoryType Type) {
using CCT = ComparisonCategoryType;
using CCR = ComparisonCategoryResult;
std::vector<CCR> 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;
}

View File

@ -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"
}

View File

@ -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<IntExprEvaluator> {
: public ExprEvaluatorBase<IntExprEvaluator> {
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 <class SuccessCB, class AfterCB>
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;
LValue LHSValue, RHSValue;
bool LHSOK = EvaluatePointer(E->getLHS(), LHSValue, Info);
if (!LHSOK && !Info.noteFailure())
return false;
bool LHSOK = EvaluatePointer(E->getLHS(), LHSValue, Info);
if (!LHSOK && !Info.noteFailure())
return false;
if (!EvaluatePointer(E->getRHS(), RHSValue, Info) || !LHSOK)
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*>();
const Expr *RHSExpr = RHSValue.Base.dyn_cast<const Expr*>();
if (!LHSExpr || !RHSExpr)
return Error(E);
const AddrLabelExpr *LHSAddrExpr = dyn_cast<AddrLabelExpr>(LHSExpr);
const AddrLabelExpr *RHSAddrExpr = dyn_cast<AddrLabelExpr>(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);
}
// 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();
const CharUnits &LHSOffset = LHSValue.getLValueOffset();
const CharUnits &RHSOffset = RHSValue.getLValueOffset();
SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator();
SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator();
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);
// 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);
QualType Type = E->getLHS()->getType();
QualType ElementType = Type->getAs<PointerType>()->getPointeeType();
CharUnits ElementSize;
if (!HandleSizeof(Info, E->getExprLoc(), ElementType, ElementSize))
return false;
// As an extension, a type may have zero size (empty struct or union in
// C, array of zero length). Pointer subtraction in such cases has
// undefined behavior, so is not constant.
if (ElementSize.isZero()) {
Info.FFDiag(E, diag::note_constexpr_pointer_subtraction_zero_size)
<< ElementType;
return false;
}
// FIXME: LLVM and GCC both compute LHSOffset - RHSOffset at runtime,
// and produce incorrect results when it overflows. Such behavior
// appears to be non-conforming, but is common, so perhaps we should
// assume the standard intended for such cases to be undefined behavior
// and check for them.
// Compute (LHSOffset - RHSOffset) / Size carefully, checking for
// overflow in the final conversion to ptrdiff_t.
APSInt LHS(
llvm::APInt(65, (int64_t)LHSOffset.getQuantity(), true), false);
APSInt RHS(
llvm::APInt(65, (int64_t)RHSOffset.getQuantity(), true), false);
APSInt ElemSize(
llvm::APInt(65, (int64_t)ElementSize.getQuantity(), true), false);
APSInt TrueResult = (LHS - RHS) / ElemSize;
APSInt Result = TrueResult.trunc(Info.Ctx.getIntWidth(E->getType()));
if (Result.extend(65) != TrueResult &&
!HandleOverflow(Info, E, TrueResult, E->getType()))
return false;
return Success(Result, E);
}
// C++11 [expr.rel]p3:
// Pointers to void (after pointer conversions) can be compared, with a
// result defined as follows: If both pointers represent the same
// 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)
// 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<CXXMethodDecl>(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<CXXMethodDecl>(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 *>();
const Expr *RHSExpr = RHSValue.Base.dyn_cast<const Expr *>();
if (!LHSExpr || !RHSExpr)
return Error(E);
const AddrLabelExpr *LHSAddrExpr = dyn_cast<AddrLabelExpr>(LHSExpr);
const AddrLabelExpr *RHSAddrExpr = dyn_cast<AddrLabelExpr>(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<PointerType>()->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 ||

View File

@ -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<ComplexType>())
ArgTy = CT->getElementType();
if (const auto *MPT = ArgTy->getAs<MemberPointerType>()) {
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<Value *, Value *> {
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);

View File

@ -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<unsigned>(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;

View File

@ -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<unsigned>(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<unsigned>(Kind)] = true;
return Info->getType();
}
/// \brief Retrieve the special "std" namespace, which may require us to
/// implicitly define the namespace.
NamespaceDecl *Sema::getOrCreateStdNamespace() {

View File

@ -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<ImplicitCastExpr>(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<EnumType>()->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<ComplexType>()) {
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);

View File

@ -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:

View File

@ -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<signed char>(__v)) {}
explicit constexpr strong_ordering(_OrdResult __v) noexcept : __value_(static_cast<signed char>(__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

View File

@ -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

View File

@ -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<signed char>(__v)) {}
explicit constexpr strong_ordering(_OrdResult __v) noexcept : __value_(static_cast<signed char>(__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

View File

@ -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

View File

@ -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<signed char>(__v)) {}
explicit constexpr strong_ordering(_OrdResult __v) noexcept : __value_(static_cast<signed char>(__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

View File

@ -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 <int Val>
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 <int>
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 <class T, T Val>
struct Conv {
constexpr operator T() const { return Val; }
operator T() { return Val; }
};
void test_user_conv() {
{
using C = Conv<int, 0>;
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<int, -1>;
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

View File

@ -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}}
constexpr void f() { // expected-error {{constant expression}}
void(1 <=> 1); // expected-note {{constant expression}}
static_assert(std::strong_ordering::equal == 0);
constexpr void f() {
void(1 <=> 1);
}
// TODO: defaulted operator <=>
struct MemPtr {
void foo() {}
void bar() {}
int data;
int data2;
long data3;
};
struct MemPtr2 {
void foo() {}
void bar() {}
int data;
int data2;
long data3;
};
using MemPtrT = void (MemPtr::*)();
using FnPtrT = void (*)();
void FnPtr1() {}
void FnPtr2() {}
#define CHECK(...) ((__VA_ARGS__) ? void() : throw "error")
#define CHECK_TYPE(...) static_assert(__is_same(__VA_ARGS__));
constexpr bool test_constexpr_success = [] {
{
auto &EQ = std::strong_ordering::equal;
auto &LESS = std::strong_ordering::less;
auto &GREATER = std::strong_ordering::greater;
using SO = std::strong_ordering;
auto eq = (42 <=> 42);
CHECK_TYPE(decltype(eq), SO);
CHECK(eq.test_eq(EQ));
auto less = (-1 <=> 0);
CHECK_TYPE(decltype(less), SO);
CHECK(less.test_eq(LESS));
auto greater = (42l <=> 1u);
CHECK_TYPE(decltype(greater), SO);
CHECK(greater.test_eq(GREATER));
}
{
using PO = std::partial_ordering;
auto EQUIV = PO::equivalent;
auto LESS = PO::less;
auto GREATER = PO::greater;
auto eq = (42.0 <=> 42.0);
CHECK_TYPE(decltype(eq), PO);
CHECK(eq.test_eq(EQUIV));
auto less = (39.0 <=> 42.0);
CHECK_TYPE(decltype(less), PO);
CHECK(less.test_eq(LESS));
auto greater = (-10.123 <=> -101.1);
CHECK_TYPE(decltype(greater), PO);
CHECK(greater.test_eq(GREATER));
}
{
using SE = std::strong_equality;
auto EQ = SE::equal;
auto NEQ = SE::nonequal;
MemPtrT P1 = &MemPtr::foo;
MemPtrT P12 = &MemPtr::foo;
MemPtrT P2 = &MemPtr::bar;
MemPtrT P3 = nullptr;
auto eq = (P1 <=> P12);
CHECK_TYPE(decltype(eq), SE);
CHECK(eq.test_eq(EQ));
auto neq = (P1 <=> P2);
CHECK_TYPE(decltype(eq), SE);
CHECK(neq.test_eq(NEQ));
auto eq2 = (P3 <=> nullptr);
CHECK_TYPE(decltype(eq2), SE);
CHECK(eq2.test_eq(EQ));
}
{
using SE = std::strong_equality;
auto EQ = SE::equal;
auto NEQ = SE::nonequal;
FnPtrT F1 = &FnPtr1;
FnPtrT F12 = &FnPtr1;
FnPtrT F2 = &FnPtr2;
FnPtrT F3 = nullptr;
auto eq = (F1 <=> F12);
CHECK_TYPE(decltype(eq), SE);
CHECK(eq.test_eq(EQ));
auto neq = (F1 <=> F2);
CHECK_TYPE(decltype(neq), SE);
CHECK(neq.test_eq(NEQ));
}
{ // mixed nullptr tests
using SO = std::strong_ordering;
using SE = std::strong_equality;
int x = 42;
int *xp = &x;
MemPtrT mf = nullptr;
MemPtrT mf2 = &MemPtr::foo;
auto r3 = (mf <=> nullptr);
CHECK_TYPE(decltype(r3), std::strong_equality);
CHECK(r3.test_eq(SE::equal));
}
return true;
}();
template <auto LHS, auto RHS, bool ExpectTrue = false>
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<nullptr, &dummy>();
constexpr bool tc2 = test_constexpr<&dummy, nullptr>();
// OK, equality comparison only
constexpr bool tc3 = test_constexpr<&MemPtr::foo, nullptr>();
constexpr bool tc4 = test_constexpr<nullptr, &MemPtr::foo>();
constexpr bool tc5 = test_constexpr<&MemPtr::foo, &MemPtr::bar>();
constexpr bool tc6 = test_constexpr<&MemPtr::data, nullptr>();
constexpr bool tc7 = test_constexpr<nullptr, &MemPtr::data>();
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 <class T, class R, class I>
constexpr T makeComplex(R r, I i) {
T res{r, i};
return res;
};
template <class T, class ResultT>
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

View File

@ -0,0 +1,65 @@
// Test diagnostics for ill-formed STL <compare> 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 <compare>}}
(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;
}