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 
20 using namespace clang;
21 
22 Optional<ComparisonCategoryType>
23 clang::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 
42 bool 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.
65 llvm::APSInt ComparisonCategoryInfo::ValueInfo::getIntValue() const {
66   assert(hasValidIntValue() && "must have a valid value");
67   return VD->evaluateValue()->getStructField(0).getInt();
68 }
69 
70 ComparisonCategoryInfo::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 
88 static 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 
99 static 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 
110 const ComparisonCategoryInfo *
111 ComparisonCategories::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 
123 const ComparisonCategoryInfo *
124 ComparisonCategories::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 
159 const 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 
165 QualType ComparisonCategoryInfo::getType() const {
166   assert(Record);
167   return QualType(Record->getTypeForDecl(), 0);
168 }
169 
170 StringRef 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 
183 StringRef 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 
200 std::vector<ComparisonCategoryResult>
201 ComparisonCategories::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