ComparisonCategories.cpp revision 360784
1//===- ComparisonCategories.cpp - Three Way Comparison Data -----*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9//  This file defines the Comparison Category enum and data types, which
10//  store the types and expressions needed to support operator<=>
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/AST/ComparisonCategories.h"
15#include "clang/AST/Decl.h"
16#include "clang/AST/DeclCXX.h"
17#include "clang/AST/Type.h"
18#include "llvm/ADT/SmallVector.h"
19
20using namespace clang;
21
22Optional<ComparisonCategoryType>
23clang::getComparisonCategoryForBuiltinCmp(QualType T) {
24  using CCT = ComparisonCategoryType;
25
26  if (T->isIntegralOrEnumerationType())
27    return CCT::StrongOrdering;
28
29  if (T->isRealFloatingType())
30    return CCT::PartialOrdering;
31
32  // C++2a [expr.spaceship]p8: If the composite pointer type is an object
33  // pointer type, p <=> q is of type std::strong_ordering.
34  // Note: this assumes neither operand is a null pointer constant.
35  if (T->isObjectPointerType())
36    return CCT::StrongOrdering;
37
38  // TODO: Extend support for operator<=> to ObjC types.
39  return llvm::None;
40}
41
42bool ComparisonCategoryInfo::ValueInfo::hasValidIntValue() const {
43  assert(VD && "must have var decl");
44  if (!VD->checkInitIsICE())
45    return false;
46
47  // Before we attempt to get the value of the first field, ensure that we
48  // actually have one (and only one) field.
49  auto *Record = VD->getType()->getAsCXXRecordDecl();
50  if (std::distance(Record->field_begin(), Record->field_end()) != 1 ||
51      !Record->field_begin()->getType()->isIntegralOrEnumerationType())
52    return false;
53
54  return true;
55}
56
57/// Attempt to determine the integer value used to represent the comparison
58/// category result by evaluating the initializer for the specified VarDecl as
59/// a constant expression and retreiving the value of the class's first
60/// (and only) field.
61///
62/// Note: The STL types are expected to have the form:
63///    struct X { T value; };
64/// where T is an integral or enumeration type.
65llvm::APSInt ComparisonCategoryInfo::ValueInfo::getIntValue() const {
66  assert(hasValidIntValue() && "must have a valid value");
67  return VD->evaluateValue()->getStructField(0).getInt();
68}
69
70ComparisonCategoryInfo::ValueInfo *ComparisonCategoryInfo::lookupValueInfo(
71    ComparisonCategoryResult ValueKind) const {
72  // Check if we already have a cache entry for this value.
73  auto It = llvm::find_if(
74      Objects, [&](ValueInfo const &Info) { return Info.Kind == ValueKind; });
75  if (It != Objects.end())
76    return &(*It);
77
78  // We don't have a cached result. Lookup the variable declaration and create
79  // a new entry representing it.
80  DeclContextLookupResult Lookup = Record->getCanonicalDecl()->lookup(
81      &Ctx.Idents.get(ComparisonCategories::getResultString(ValueKind)));
82  if (Lookup.empty() || !isa<VarDecl>(Lookup.front()))
83    return nullptr;
84  Objects.emplace_back(ValueKind, cast<VarDecl>(Lookup.front()));
85  return &Objects.back();
86}
87
88static const NamespaceDecl *lookupStdNamespace(const ASTContext &Ctx,
89                                               NamespaceDecl *&StdNS) {
90  if (!StdNS) {
91    DeclContextLookupResult Lookup =
92        Ctx.getTranslationUnitDecl()->lookup(&Ctx.Idents.get("std"));
93    if (!Lookup.empty())
94      StdNS = dyn_cast<NamespaceDecl>(Lookup.front());
95  }
96  return StdNS;
97}
98
99static CXXRecordDecl *lookupCXXRecordDecl(const ASTContext &Ctx,
100                                          const NamespaceDecl *StdNS,
101                                          ComparisonCategoryType Kind) {
102  StringRef Name = ComparisonCategories::getCategoryString(Kind);
103  DeclContextLookupResult Lookup = StdNS->lookup(&Ctx.Idents.get(Name));
104  if (!Lookup.empty())
105    if (CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(Lookup.front()))
106      return RD;
107  return nullptr;
108}
109
110const ComparisonCategoryInfo *
111ComparisonCategories::lookupInfo(ComparisonCategoryType Kind) const {
112  auto It = Data.find(static_cast<char>(Kind));
113  if (It != Data.end())
114    return &It->second;
115
116  if (const NamespaceDecl *NS = lookupStdNamespace(Ctx, StdNS))
117    if (CXXRecordDecl *RD = lookupCXXRecordDecl(Ctx, NS, Kind))
118      return &Data.try_emplace((char)Kind, Ctx, RD, Kind).first->second;
119
120  return nullptr;
121}
122
123const ComparisonCategoryInfo *
124ComparisonCategories::lookupInfoForType(QualType Ty) const {
125  assert(!Ty.isNull() && "type must be non-null");
126  using CCT = ComparisonCategoryType;
127  auto *RD = Ty->getAsCXXRecordDecl();
128  if (!RD)
129    return nullptr;
130
131  // Check to see if we have information for the specified type cached.
132  const auto *CanonRD = RD->getCanonicalDecl();
133  for (auto &KV : Data) {
134    const ComparisonCategoryInfo &Info = KV.second;
135    if (CanonRD == Info.Record->getCanonicalDecl())
136      return &Info;
137  }
138
139  if (!RD->getEnclosingNamespaceContext()->isStdNamespace())
140    return nullptr;
141
142  // If not, check to see if the decl names a type in namespace std with a name
143  // matching one of the comparison category types.
144  for (unsigned I = static_cast<unsigned>(CCT::First),
145                End = static_cast<unsigned>(CCT::Last);
146       I <= End; ++I) {
147    CCT Kind = static_cast<CCT>(I);
148
149    // We've found the comparison category type. Build a new cache entry for
150    // it.
151    if (getCategoryString(Kind) == RD->getName())
152      return &Data.try_emplace((char)Kind, Ctx, RD, Kind).first->second;
153  }
154
155  // We've found nothing. This isn't a comparison category type.
156  return nullptr;
157}
158
159const ComparisonCategoryInfo &ComparisonCategories::getInfoForType(QualType Ty) const {
160  const ComparisonCategoryInfo *Info = lookupInfoForType(Ty);
161  assert(Info && "info for comparison category not found");
162  return *Info;
163}
164
165QualType ComparisonCategoryInfo::getType() const {
166  assert(Record);
167  return QualType(Record->getTypeForDecl(), 0);
168}
169
170StringRef ComparisonCategories::getCategoryString(ComparisonCategoryType Kind) {
171  using CCKT = ComparisonCategoryType;
172  switch (Kind) {
173  case CCKT::PartialOrdering:
174    return "partial_ordering";
175  case CCKT::WeakOrdering:
176    return "weak_ordering";
177  case CCKT::StrongOrdering:
178    return "strong_ordering";
179  }
180  llvm_unreachable("unhandled cases in switch");
181}
182
183StringRef ComparisonCategories::getResultString(ComparisonCategoryResult Kind) {
184  using CCVT = ComparisonCategoryResult;
185  switch (Kind) {
186  case CCVT::Equal:
187    return "equal";
188  case CCVT::Equivalent:
189    return "equivalent";
190  case CCVT::Less:
191    return "less";
192  case CCVT::Greater:
193    return "greater";
194  case CCVT::Unordered:
195    return "unordered";
196  }
197  llvm_unreachable("unhandled case in switch");
198}
199
200std::vector<ComparisonCategoryResult>
201ComparisonCategories::getPossibleResultsForType(ComparisonCategoryType Type) {
202  using CCT = ComparisonCategoryType;
203  using CCR = ComparisonCategoryResult;
204  std::vector<CCR> Values;
205  Values.reserve(4);
206  bool IsStrong = Type == CCT::StrongOrdering;
207  Values.push_back(IsStrong ? CCR::Equal : CCR::Equivalent);
208  Values.push_back(CCR::Less);
209  Values.push_back(CCR::Greater);
210  if (Type == CCT::PartialOrdering)
211    Values.push_back(CCR::Unordered);
212  return Values;
213}
214