blob: 45956708a94b33c5a58116879dddcf96a40de8fa [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "lifetime_annotations/type_lifetimes.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "lifetime_annotations/function_lifetimes.h"
#include "lifetime_annotations/lifetime.h"
#include "lifetime_annotations/lifetime_symbol_table.h"
#include "lifetime_annotations/pointee_type.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/TemplateBase.h"
#include "clang/AST/Type.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
namespace clang {
namespace tidy {
namespace lifetimes {
llvm::SmallVector<std::string> GetLifetimeParameters(clang::QualType type) {
// TODO(mboehme):
// - Add support for type aliases with lifetime parameters
// - Report errors as Clang diagnostics, not through
// llvm::report_fatal_error().
auto record = type->getAs<clang::RecordType>();
if (!record) {
return {};
}
auto cxx_record = record->getAsCXXRecordDecl();
if (!cxx_record) {
return {};
}
const clang::AnnotateAttr* lifetime_params_attr = nullptr;
for (auto annotate : cxx_record->specific_attrs<clang::AnnotateAttr>()) {
if (annotate->getAnnotation() == "lifetime_params") {
if (lifetime_params_attr) {
llvm::report_fatal_error("repeated lifetime annotation");
}
lifetime_params_attr = annotate;
}
}
llvm::SmallVector<std::string> ret;
// TODO(mboehme): Require derived class to explicitly declare all lifetime
// parameters inherited from the base class.
if (cxx_record->hasDefinition()) {
for (const clang::CXXBaseSpecifier& base : cxx_record->bases()) {
if (lifetime_params_attr) {
llvm::report_fatal_error(
"derived classes may not add lifetime parameters");
}
if (!ret.empty()) {
llvm::report_fatal_error(
"only one base class may have lifetime parameters");
}
ret = GetLifetimeParameters(base.getType());
}
}
if (!lifetime_params_attr) {
return ret;
}
for (const auto& arg : lifetime_params_attr->args()) {
llvm::StringRef lifetime;
if (llvm::Error err =
EvaluateAsStringLiteral(arg, cxx_record->getASTContext())
.moveInto(lifetime)) {
llvm::report_fatal_error(llvm::StringRef(toString(std::move(err))));
}
ret.push_back(lifetime.str());
}
return ret;
}
ValueLifetimes::ValueLifetimes(const ValueLifetimes& other) { *this = other; }
ValueLifetimes& ValueLifetimes::operator=(const ValueLifetimes& other) {
type_ = other.type_;
template_argument_lifetimes_ = other.template_argument_lifetimes_;
lifetime_parameters_by_name_ = other.lifetime_parameters_by_name_;
pointee_lifetimes_ =
other.pointee_lifetimes_
? std::make_unique<ObjectLifetimes>(*other.pointee_lifetimes_)
: nullptr;
function_lifetimes_ =
other.function_lifetimes_
? std::make_unique<FunctionLifetimes>(*other.function_lifetimes_)
: nullptr;
return *this;
}
// Defined here because FunctionLifetimes is an incomplete type in the header.
ValueLifetimes::~ValueLifetimes() = default;
namespace {
llvm::Error ForEachTemplateArgument(
clang::QualType type,
const std::function<llvm::Error(int, clang::QualType)>& callback) {
llvm::SmallVector<llvm::ArrayRef<clang::TemplateArgument>> template_args =
GetTemplateArgs(type);
for (size_t depth = 0; depth < template_args.size(); depth++) {
const auto& args_at_depth = template_args[depth];
for (const clang::TemplateArgument& arg : args_at_depth) {
if (arg.getKind() == clang::TemplateArgument::Type) {
if (llvm::Error err = callback(depth, arg.getAsType())) {
return err;
}
} else if (arg.getKind() == clang::TemplateArgument::Pack) {
for (const clang::TemplateArgument& inner_arg : arg.getPackAsArray()) {
if (inner_arg.getKind() == clang::TemplateArgument::Type) {
if (llvm::Error err = callback(depth, inner_arg.getAsType())) {
return err;
}
}
}
} else {
if (llvm::Error err = callback(depth, clang::QualType())) {
return err;
}
}
}
}
return llvm::Error::success();
}
} // namespace
llvm::Expected<ValueLifetimes> ValueLifetimes::Create(
clang::QualType type, LifetimeFactory lifetime_factory) {
assert(!type.isNull());
type = type.IgnoreParens();
ValueLifetimes ret(type);
if (const auto* fn = clang::dyn_cast<clang::FunctionProtoType>(type)) {
// TODO(veluca): this will not correctly handle the distinction between
// parameter and return lifetimes.
FunctionLifetimeFactorySingleCallback factory(lifetime_factory);
FunctionLifetimes fn_lftm;
if (llvm::Error err = FunctionLifetimes::CreateForFunctionType(fn, factory)
.moveInto(fn_lftm)) {
return std::move(err);
}
ret.function_lifetimes_ =
std::make_unique<FunctionLifetimes>(std::move(fn_lftm));
return ret;
}
for (const auto& lftm_param : GetLifetimeParameters(type)) {
Lifetime l;
if (llvm::Error err = lifetime_factory(type, lftm_param).moveInto(l)) {
return std::move(err);
}
ret.lifetime_parameters_by_name_.Add(lftm_param, l);
}
// Add implicit lifetime parameters for type template parameters.
if (llvm::Error err = ForEachTemplateArgument(
type,
[&ret, &lifetime_factory](int depth,
clang::QualType arg_type) -> llvm::Error {
std::optional<ValueLifetimes> maybe_template_arg_lifetime;
if (!arg_type.isNull()) {
maybe_template_arg_lifetime.emplace();
if (llvm::Error err =
ValueLifetimes::Create(arg_type, lifetime_factory)
.moveInto(*maybe_template_arg_lifetime)) {
return err;
}
}
if (ret.template_argument_lifetimes_.size() <= depth) {
ret.template_argument_lifetimes_.resize(depth + 1);
}
ret.template_argument_lifetimes_[depth].push_back(
maybe_template_arg_lifetime);
return llvm::Error::success();
})) {
return std::move(err);
}
clang::QualType pointee = PointeeType(type);
if (pointee.isNull()) return ret;
ObjectLifetimes obj_lftm;
if (llvm::Error err = ObjectLifetimes::Create(pointee, lifetime_factory)
.moveInto(obj_lftm)) {
return std::move(err);
}
ret.pointee_lifetimes_ =
std::make_unique<ObjectLifetimes>(std::move(obj_lftm));
return ret;
}
llvm::Expected<ObjectLifetimes> ObjectLifetimes::Create(
clang::QualType type, LifetimeFactory lifetime_factory) {
ValueLifetimes v;
if (llvm::Error err =
ValueLifetimes::Create(type, lifetime_factory).moveInto(v)) {
return std::move(err);
}
Lifetime l;
if (llvm::Error err = lifetime_factory(type, "").moveInto(l)) {
return std::move(err);
}
return ObjectLifetimes(l, v);
}
ValueLifetimes ValueLifetimes::ForLifetimeLessType(clang::QualType type) {
assert(PointeeType(type).isNull() && !type->isRecordType());
return ValueLifetimes(type);
}
ValueLifetimes ValueLifetimes::ForPointerLikeType(
clang::QualType type, const ObjectLifetimes& object_lifetimes) {
assert(!PointeeType(type).isNull());
ValueLifetimes result(type);
result.pointee_lifetimes_ =
std::make_unique<ObjectLifetimes>(object_lifetimes);
return result;
}
ValueLifetimes ValueLifetimes::ForRecord(
clang::QualType type,
std::vector<std::vector<std::optional<ValueLifetimes>>>
template_argument_lifetimes,
LifetimeSymbolTable lifetime_parameters) {
assert(type->isRecordType());
assert(GetTemplateArgs(type).size() == template_argument_lifetimes.size());
for (size_t depth = 0; depth < template_argument_lifetimes.size(); ++depth) {
assert(GetTemplateArgs(type)[depth].size() ==
template_argument_lifetimes[depth].size());
}
ValueLifetimes result(type);
result.template_argument_lifetimes_ = std::move(template_argument_lifetimes);
result.lifetime_parameters_by_name_ = lifetime_parameters;
return result;
}
std::string ValueLifetimes::DebugString(
const LifetimeFormatter& formatter) const {
if (!PointeeType(Type()).isNull()) {
assert(pointee_lifetimes_);
return pointee_lifetimes_->DebugString(formatter);
}
if (clang::isa<clang::FunctionProtoType>(Type())) {
assert(function_lifetimes_);
std::string fn_lifetimes = function_lifetimes_->DebugString(formatter);
if (fn_lifetimes.empty()) return "";
return absl::StrCat("(", fn_lifetimes, ")");
}
std::vector<std::vector<std::string>> tmpl_lifetimes;
for (auto& tmpl_arg_at_depth : template_argument_lifetimes_) {
tmpl_lifetimes.emplace_back();
for (const std::optional<ValueLifetimes>& tmpl_arg : tmpl_arg_at_depth) {
if (tmpl_arg) {
std::string inner = tmpl_arg->DebugString(formatter);
if (!inner.empty()) {
tmpl_lifetimes.back().push_back(std::move(inner));
}
}
}
}
std::vector<std::string> lifetime_parameters;
for (const auto& lftm_arg : GetLifetimeParameters(type_)) {
std::optional<Lifetime> lifetime =
lifetime_parameters_by_name_.LookupName(lftm_arg);
assert(lifetime.has_value());
lifetime_parameters.push_back(formatter(*lifetime));
}
bool empty_tmpl_lifetimes = true;
for (const auto& tmpl_arg_at_depth : tmpl_lifetimes) {
for (const auto& tmpl : tmpl_arg_at_depth) {
empty_tmpl_lifetimes &= tmpl.empty();
}
}
if (empty_tmpl_lifetimes && lifetime_parameters.empty()) {
return "";
} else if (empty_tmpl_lifetimes) {
if (lifetime_parameters.size() == 1) {
return lifetime_parameters[0];
}
return absl::StrCat("[", absl::StrJoin(lifetime_parameters, ", "), "]");
} else if (lifetime_parameters.empty() && tmpl_lifetimes.size() == 1 &&
tmpl_lifetimes[0].size() == 1) {
return tmpl_lifetimes[0][0];
}
std::vector<std::string> tmpl_lifetimes_per_depth;
tmpl_lifetimes_per_depth.reserve(tmpl_lifetimes.size());
for (const auto& tmpl_depth : tmpl_lifetimes) {
tmpl_lifetimes_per_depth.push_back(
absl::StrCat("<", absl::StrJoin(tmpl_depth, ", "), ">"));
}
std::string lifetime_parameter_string;
if (!lifetime_parameters.empty()) {
lifetime_parameter_string =
absl::StrCat(" [", absl::StrJoin(lifetime_parameters, ", "), "]");
}
return absl::StrCat(absl::StrJoin(tmpl_lifetimes_per_depth, "::"),
lifetime_parameter_string);
}
const ObjectLifetimes& ValueLifetimes::GetPointeeLifetimes() const {
assert(!PointeeType(type_).isNull());
return *pointee_lifetimes_;
}
bool ValueLifetimes::HasAny(
const std::function<bool(Lifetime)>& predicate) const {
for (const auto& tmpl_arg_at_depth : template_argument_lifetimes_) {
for (const std::optional<ValueLifetimes>& tmpl_arg : tmpl_arg_at_depth) {
if (tmpl_arg && tmpl_arg->HasAny(predicate)) {
return true;
}
}
}
if (pointee_lifetimes_ && pointee_lifetimes_->HasAny(predicate)) {
return true;
}
for (const auto& lftm_arg : GetLifetimeParameters(type_)) {
std::optional<Lifetime> lifetime =
lifetime_parameters_by_name_.LookupName(lftm_arg);
assert(lifetime.has_value());
if (predicate(lifetime.value())) {
return true;
}
}
if (function_lifetimes_ && function_lifetimes_->HasAny(predicate)) {
return true;
}
return false;
}
void ValueLifetimes::SubstituteLifetimes(const LifetimeSubstitutions& subst) {
for (auto& tmpl_arg_at_depth : template_argument_lifetimes_) {
for (std::optional<ValueLifetimes>& tmpl_arg : tmpl_arg_at_depth) {
if (tmpl_arg) {
tmpl_arg->SubstituteLifetimes(subst);
}
}
}
if (pointee_lifetimes_) {
pointee_lifetimes_->SubstituteLifetimes(subst);
}
for (const auto& lftm_arg : GetLifetimeParameters(type_)) {
std::optional<Lifetime> lifetime =
lifetime_parameters_by_name_.LookupName(lftm_arg);
assert(lifetime.has_value());
lifetime_parameters_by_name_.Rebind(lftm_arg, subst.Substitute(*lifetime));
}
if (function_lifetimes_) {
function_lifetimes_->SubstituteLifetimes(subst);
}
}
void ValueLifetimes::Traverse(std::function<void(Lifetime&, Variance)> visitor,
Variance variance) {
for (auto& tmpl_arg_at_depth : template_argument_lifetimes_) {
for (std::optional<ValueLifetimes>& tmpl_arg : tmpl_arg_at_depth) {
if (tmpl_arg) {
tmpl_arg->Traverse(visitor, kInvariant);
}
}
}
if (pointee_lifetimes_) {
pointee_lifetimes_->Traverse(visitor, variance, Type());
}
for (const auto& lftm_arg : GetLifetimeParameters(type_)) {
std::optional<Lifetime> lifetime =
lifetime_parameters_by_name_.LookupName(lftm_arg);
assert(lifetime.has_value());
Lifetime new_lifetime = *lifetime;
visitor(new_lifetime, variance);
// Note that this check is not an optimization, but a guard against UB:
// if one calls the const version of Traverse, with a callback that does not
// mutate the lifetime, on a const instance of the Value/ObjectLifetimes,
// then calling Rebind would be UB, even if Rebind would do nothing in
// practice.
if (new_lifetime != lifetime) {
lifetime_parameters_by_name_.Rebind(lftm_arg, new_lifetime);
}
}
if (function_lifetimes_) {
function_lifetimes_->Traverse(visitor);
}
}
void ValueLifetimes::Traverse(
std::function<void(const Lifetime&, Variance)> visitor,
Variance variance) const {
const_cast<ValueLifetimes*>(this)->Traverse(
[&visitor](Lifetime& l, Variance v) { visitor(l, v); }, variance);
}
ValueLifetimes::ValueLifetimes(clang::QualType type) : type_(type) {}
const llvm::SmallVector<llvm::ArrayRef<clang::TemplateArgument>>
GetTemplateArgs(clang::QualType type) {
llvm::SmallVector<llvm::ArrayRef<clang::TemplateArgument>> result;
// Desugar any typedefs on `type`. We need to do this so that the "depth" of
// the template arguments is compatible with
// TemplateTypeParmType::getDepth(), which likewise assumes that typedefs are
// desugared; see the call to getCanonicalTypeInternal() in
// TemplateTypeParmType::getCanTTPTInfo(). Unlike that function, we can't
// simply canonicalize the type, as that would also remove type annotations,
// and we need those.
while (const auto* typedef_type = type->getAs<clang::TypedefType>()) {
type = typedef_type->desugar();
}
if (auto elaborated = type->getAs<clang::ElaboratedType>()) {
if (clang::NestedNameSpecifier* qualifier = elaborated->getQualifier()) {
if (const clang::Type* qualifier_type = qualifier->getAsType()) {
result = GetTemplateArgs(clang::QualType(qualifier_type, 0));
}
}
}
if (auto specialization = type->getAs<clang::TemplateSpecializationType>()) {
result.push_back(specialization->template_arguments());
} else if (auto record = type->getAs<clang::RecordType>()) {
if (auto specialization_decl =
clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(
record->getDecl())) {
result.push_back(specialization_decl->getTemplateArgs().asArray());
}
}
return result;
}
ObjectLifetimes ObjectLifetimes::GetObjectLifetimesForTypeInContext(
clang::QualType type, llvm::SmallVector<std::string> type_lifetime_args,
llvm::StringRef object_lifetime_parameter) const {
assert(value_lifetimes_.Type()->isRecordType());
// The object of the `type` (i.e. field or a base class) basically has the
// same lifetime as the struct.
Lifetime ret_lifetime = lifetime_;
// ... unless the field has lifetime parameters.
if (!object_lifetime_parameter.empty()) {
ret_lifetime =
value_lifetimes_.GetLifetimeParameter(object_lifetime_parameter);
}
// `type` is one of a template argument, a struct, a pointer, or a type
// with no lifetimes (other than its own).
// First case: template argument. We just attach the
// template argument's lifetimes to the leaf ObjectLifetimes.
if (auto targ = type->getAs<clang::SubstTemplateTypeParmType>()) {
const clang::TemplateTypeParmType* type_parm = targ->getReplacedParameter();
const std::optional<ValueLifetimes>& arg_lifetimes =
value_lifetimes_.GetTemplateArgumentLifetimes(type_parm->getDepth(),
type_parm->getIndex());
if (!arg_lifetimes) {
assert(false);
return llvm::DenseMapInfo<ObjectLifetimes>::getEmptyKey();
}
return ObjectLifetimes(ret_lifetime, *arg_lifetimes);
} else if (type->isStructureOrClassType()) {
// Second case: struct.
// Resolve lifetime parameters for the struct, if it has any.
LifetimeSymbolTable lifetime_params;
llvm::SmallVector<std::string> params = GetLifetimeParameters(type);
for (size_t i = params.size(); i-- > 0;) {
assert(!type_lifetime_args.empty());
auto lftm_arg = type_lifetime_args.back();
type_lifetime_args.pop_back();
lifetime_params.Add(params[i],
value_lifetimes_.GetLifetimeParameter(lftm_arg));
}
// We need to construct potentally reshuffled
// template arguments, if the struct is a template.
// TODO(veluca): mixing lifetimes and template parameters is not supported
// yet.
std::vector<std::vector<std::optional<ValueLifetimes>>>
template_argument_lifetimes;
for (const auto& args_at_depth : GetTemplateArgs(type)) {
size_t parameter_pack_expansion_index = 0;
template_argument_lifetimes.push_back({});
for (const clang::TemplateArgument& arg : args_at_depth) {
if (arg.getKind() == clang::TemplateArgument::Type) {
if (auto templ_arg =
clang::dyn_cast<clang::SubstTemplateTypeParmType>(
arg.getAsType())) {
const clang::TemplateTypeParmType* type_parm =
templ_arg->getReplacedParameter();
// Template parameter packs get the index of the *pack*, not the
// index of the type inside the pack itself. As they must appear
// last, we can just increase the counter at every occurrence and
// wraparound when we run out of template arguments.
size_t index = type_parm->getIndex();
if (type_parm->isParameterPack()) {
index += parameter_pack_expansion_index++;
if (index + 1 >= value_lifetimes_.GetNumTemplateArgumentsAtDepth(
type_parm->getDepth())) {
parameter_pack_expansion_index = 0;
}
}
template_argument_lifetimes.back().push_back(
value_lifetimes_.GetTemplateArgumentLifetimes(
type_parm->getDepth(), index));
} else {
// Create a new ValueLifetimes of the type of the template
// parameter, with lifetime `lifetime_`.
// TODO(veluca): we need to propagate lifetime parameters here.
template_argument_lifetimes.back().push_back(
ValueLifetimes::Create(arg.getAsType(), [this](
clang::QualType,
llvm::StringRef) {
return this->lifetime_;
}).get());
}
} else {
template_argument_lifetimes.back().push_back(std::nullopt);
}
}
}
return ObjectLifetimes(
ret_lifetime,
ValueLifetimes::ForRecord(type, std::move(template_argument_lifetimes),
std::move(lifetime_params)));
} else if (clang::QualType pointee_type = PointeeType(type);
!pointee_type.isNull()) {
if (type_lifetime_args.empty()) {
llvm::report_fatal_error(
llvm::Twine("didn't find type lifetimes for object of type " +
type.getAsString()));
}
std::string pointee_object_lifetime_parameter =
std::move(type_lifetime_args.back());
type_lifetime_args.pop_back();
// Third case: pointer.
return ObjectLifetimes(
ret_lifetime, ValueLifetimes::ForPointerLikeType(
type, GetObjectLifetimesForTypeInContext(
pointee_type, std::move(type_lifetime_args),
pointee_object_lifetime_parameter)));
}
return ObjectLifetimes(ret_lifetime,
ValueLifetimes::ForLifetimeLessType(type));
}
std::string ObjectLifetimes::DebugString(
const LifetimeFormatter& formatter) const {
std::string inner_lifetimes = value_lifetimes_.DebugString(formatter);
std::string lifetime = formatter(lifetime_);
if (inner_lifetimes.empty()) {
return lifetime;
}
return absl::StrCat(std::move(inner_lifetimes), ", ", std::move(lifetime));
}
ObjectLifetimes ObjectLifetimes::GetFieldOrBaseLifetimes(
clang::QualType type,
llvm::SmallVector<std::string> type_lifetime_args) const {
return GetObjectLifetimesForTypeInContext(type, std::move(type_lifetime_args),
"");
}
bool ObjectLifetimes::HasAny(
const std::function<bool(Lifetime)>& predicate) const {
return predicate(lifetime_) || value_lifetimes_.HasAny(predicate);
}
void ObjectLifetimes::SubstituteLifetimes(const LifetimeSubstitutions& subst) {
lifetime_ = subst.Substitute(lifetime_);
value_lifetimes_.SubstituteLifetimes(subst);
}
void ObjectLifetimes::Traverse(std::function<void(Lifetime&, Variance)> visitor,
Variance variance,
clang::QualType indirection_type) {
assert(indirection_type.isNull() ||
indirection_type->getPointeeType().IgnoreParens() == Type());
value_lifetimes_.Traverse(
visitor, indirection_type.isNull() || indirection_type.isConstQualified()
? kCovariant
: kInvariant);
visitor(lifetime_, variance);
}
void ObjectLifetimes::Traverse(
std::function<void(const Lifetime&, Variance)> visitor, Variance variance,
clang::QualType indirection_type) const {
const_cast<ObjectLifetimes*>(this)->Traverse(
[&visitor](Lifetime& l, Variance v) { visitor(l, v); }, variance,
indirection_type);
}
llvm::Expected<llvm::StringRef> EvaluateAsStringLiteral(
const clang::Expr* expr, const clang::ASTContext& ast_context) {
auto error = []() {
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
"cannot evaluate argument as a string literal");
};
clang::Expr::EvalResult eval_result;
if (!expr->EvaluateAsConstantExpr(eval_result, ast_context) ||
!eval_result.Val.isLValue()) {
return error();
}
const auto* eval_result_expr =
eval_result.Val.getLValueBase().dyn_cast<const clang::Expr*>();
if (!eval_result_expr) {
return error();
}
const auto* strlit = clang::dyn_cast<clang::StringLiteral>(eval_result_expr);
if (!strlit) {
return error();
}
return strlit->getString();
}
} // namespace lifetimes
} // namespace tidy
} // namespace clang
namespace llvm {
bool DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes>::isEqual(
const clang::tidy::lifetimes::ValueLifetimes& lhs,
const clang::tidy::lifetimes::ValueLifetimes& rhs) {
if (lhs.type_ != rhs.type_) {
return false;
}
if ((lhs.pointee_lifetimes_ == nullptr) !=
(rhs.pointee_lifetimes_ == nullptr)) {
return false;
}
if (lhs.pointee_lifetimes_ &&
!DenseMapInfo<clang::tidy::lifetimes::ObjectLifetimes>::isEqual(
*lhs.pointee_lifetimes_, *rhs.pointee_lifetimes_)) {
return false;
}
if (lhs.template_argument_lifetimes_.size() !=
rhs.template_argument_lifetimes_.size()) {
return false;
}
for (size_t i = 0; i < lhs.template_argument_lifetimes_.size(); i++) {
if (lhs.template_argument_lifetimes_[i].size() !=
rhs.template_argument_lifetimes_[i].size()) {
return false;
}
for (size_t j = 0; j < lhs.template_argument_lifetimes_[i].size(); j++) {
const auto& alhs = lhs.template_argument_lifetimes_[i][j];
const auto& arhs = rhs.template_argument_lifetimes_[i][j];
if (alhs.has_value() != arhs.has_value()) {
return false;
}
if (alhs.has_value() && !isEqual(*alhs, *arhs)) {
return false;
}
}
}
if (lhs.lifetime_parameters_by_name_.GetMapping() !=
rhs.lifetime_parameters_by_name_.GetMapping()) {
return false;
}
return true;
}
unsigned DenseMapInfo<clang::tidy::lifetimes::ValueLifetimes>::getHashValue(
const clang::tidy::lifetimes::ValueLifetimes& value_lifetimes) {
llvm::hash_code hash = 0;
if (value_lifetimes.pointee_lifetimes_) {
hash = DenseMapInfo<clang::tidy::lifetimes::ObjectLifetimes>::getHashValue(
*value_lifetimes.pointee_lifetimes_);
}
for (const auto& lifetimes_at_depth :
value_lifetimes.template_argument_lifetimes_) {
for (const auto& tmpl_lifetime : lifetimes_at_depth) {
if (tmpl_lifetime) {
hash = hash_combine(hash, getHashValue(*tmpl_lifetime));
}
}
}
for (const auto& lifetime_arg :
value_lifetimes.lifetime_parameters_by_name_.GetMapping()) {
hash = hash_combine(hash, DenseMapInfo<llvm::StringRef>::getHashValue(
lifetime_arg.first()));
hash = hash_combine(
hash, DenseMapInfo<clang::tidy::lifetimes::Lifetime>::getHashValue(
lifetime_arg.second));
}
return hash_combine(
hash, DenseMapInfo<clang::QualType>::getHashValue(value_lifetimes.type_));
}
} // namespace llvm