| //===- ComparisonCategories.cpp - Three Way Comparison Data -----*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // 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/ASTContext.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/Type.h" |
| #include "llvm/ADT/SmallVector.h" |
| |
| using namespace clang; |
| |
| Optional<ComparisonCategoryType> |
| clang::getComparisonCategoryForBuiltinCmp(QualType T) { |
| using CCT = ComparisonCategoryType; |
| |
| if (T->isIntegralOrEnumerationType()) |
| return CCT::StrongOrdering; |
| |
| if (T->isRealFloatingType()) |
| return CCT::PartialOrdering; |
| |
| // C++2a [expr.spaceship]p8: If the composite pointer type is an object |
| // pointer type, p <=> q is of type std::strong_ordering. |
| // Note: this assumes neither operand is a null pointer constant. |
| if (T->isObjectPointerType()) |
| return CCT::StrongOrdering; |
| |
| // TODO: Extend support for operator<=> to ObjC types. |
| return llvm::None; |
| } |
| |
| bool ComparisonCategoryInfo::ValueInfo::hasValidIntValue() const { |
| assert(VD && "must have var decl"); |
| if (!VD->isUsableInConstantExpressions(VD->getASTContext())) |
| 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 = VD->getType()->getAsCXXRecordDecl(); |
| if (std::distance(Record->field_begin(), Record->field_end()) != 1 || |
| !Record->field_begin()->getType()->isIntegralOrEnumerationType()) |
| return false; |
| |
| return true; |
| } |
| |
| /// 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 retrieving the value of the class's 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. |
| llvm::APSInt ComparisonCategoryInfo::ValueInfo::getIntValue() const { |
| assert(hasValidIntValue() && "must have a valid value"); |
| return VD->evaluateValue()->getStructField(0).getInt(); |
| } |
| |
| 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; }); |
| if (It != Objects.end()) |
| return &(*It); |
| |
| // We don't have a cached result. Lookup the variable declaration and create |
| // a new entry representing it. |
| DeclContextLookupResult Lookup = Record->getCanonicalDecl()->lookup( |
| &Ctx.Idents.get(ComparisonCategories::getResultString(ValueKind))); |
| if (Lookup.empty() || !isa<VarDecl>(Lookup.front())) |
| return nullptr; |
| Objects.emplace_back(ValueKind, cast<VarDecl>(Lookup.front())); |
| return &Objects.back(); |
| } |
| |
| static const NamespaceDecl *lookupStdNamespace(const ASTContext &Ctx, |
| NamespaceDecl *&StdNS) { |
| if (!StdNS) { |
| DeclContextLookupResult Lookup = |
| Ctx.getTranslationUnitDecl()->lookup(&Ctx.Idents.get("std")); |
| if (!Lookup.empty()) |
| 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.empty()) |
| 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::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::Equivalent: |
| return "equivalent"; |
| 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(4); |
| bool IsStrong = Type == CCT::StrongOrdering; |
| Values.push_back(IsStrong ? CCR::Equal : CCR::Equivalent); |
| Values.push_back(CCR::Less); |
| Values.push_back(CCR::Greater); |
| if (Type == CCT::PartialOrdering) |
| Values.push_back(CCR::Unordered); |
| return Values; |
| } |