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