blob: 378d7687b6032837f40b588e5f1ccf1f5930a962 [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 "rs_bindings_from_cc/ast_visitor.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/logging.h"
#include "rs_bindings_from_cc/ast_convert.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
#include "third_party/absl/container/flat_hash_map.h"
#include "third_party/absl/container/flat_hash_set.h"
#include "third_party/absl/status/status.h"
#include "third_party/absl/status/statusor.h"
#include "third_party/absl/strings/cord.h"
#include "third_party/absl/strings/str_cat.h"
#include "third_party/absl/strings/string_view.h"
#include "third_party/absl/strings/substitute.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/ASTContext.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Decl.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclTemplate.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Mangle.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/RawCommentList.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/RecordLayout.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Type.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/SourceLocation.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/SourceManager.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/Specifiers.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Sema/Sema.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
#include "util/gtl/flat_map.h"
namespace rs_bindings_from_cc {
constexpr std::string_view kTypeStatusPayloadUrl =
"type.googleapis.com/devtools.rust.cc_interop.rs_binding_from_cc.type";
// A mapping of C++ standard types to their equivalent Rust types.
// To produce more idiomatic results, these types receive special handling
// instead of using the generic type mapping mechanism.
static constexpr auto kWellKnownTypes =
gtl::fixed_flat_map_of<absl::string_view, absl::string_view>({
{"ptrdiff_t", "isize"},
{"intptr_t", "isize"},
{"size_t", "usize"},
{"uintptr_t", "usize"},
{"int8_t", "i8"},
{"uint8_t", "u8"},
{"int16_t", "i16"},
{"uint16_t", "u16"},
{"int32_t", "i32"},
{"uint32_t", "u32"},
{"int64_t", "i64"},
{"uint64_t", "u64"},
{"char16_t", "u16"},
{"char32_t", "u32"},
{"wchar_t", "i32"},
});
static DeclId GenerateDeclId(const clang::Decl* decl) {
return DeclId(reinterpret_cast<uintptr_t>(decl->getCanonicalDecl()));
}
bool AstVisitor::TraverseDecl(clang::Decl* decl) {
// TODO(mboehme): I'm not sure if TraverseDecl() is supposed to be called with
// null pointers or whether this is a bug in RecursiveASTVisitor, but I've
// seen null pointers occur here in practice. In the case where this occurred,
// TraverseDecl was being called from TraverseTemplateTemplateParmDecl().
if (!decl) {
return true;
}
// Skip declarations that we've already seen, except for namespaces, which
// can and typically will contain new declarations when they are "reopened".
if (!seen_decls_.insert(decl->getCanonicalDecl()).second &&
!clang::isa<clang::NamespaceDecl>(decl)) {
return true;
}
const clang::DeclContext* decl_context = decl->getDeclContext();
if (decl_context && decl_context->isNamespace()) {
PushUnsupportedItem(decl,
"Items contained in namespaces are not supported yet",
decl->getBeginLoc());
return true;
}
// Emit all comments in the current file before the decl
comment_manager_.TraverseDecl(decl);
return Base::TraverseDecl(decl);
}
bool AstVisitor::TraverseTranslationUnitDecl(
clang::TranslationUnitDecl* translation_unit_decl) {
ctx_ = &translation_unit_decl->getASTContext();
mangler_.reset(ctx_->createMangleContext());
for (const HeaderName& header_name : public_header_names_) {
ir_.used_headers.push_back(header_name);
}
ir_.current_target = current_target_;
bool result = Base::TraverseTranslationUnitDecl(translation_unit_decl);
// Emit comments after the last decl
comment_manager_.FlushComments();
return result;
}
bool AstVisitor::VisitFunctionDecl(clang::FunctionDecl* function_decl) {
if (!IsFromCurrentTarget(function_decl)) return true;
if (function_decl->isDeleted()) return true;
devtools_rust::LifetimeSymbolTable lifetime_symbol_table;
llvm::Expected<devtools_rust::FunctionLifetimes> lifetimes =
devtools_rust::GetLifetimeAnnotations(function_decl, lifetime_context_,
&lifetime_symbol_table);
llvm::DenseSet<devtools_rust::Lifetime> all_lifetimes;
std::vector<FuncParam> params;
bool success = true;
// non-static member functions receive an implicit `this` parameter.
if (auto* method_decl = llvm::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
if (method_decl->isInstance()) {
std::optional<devtools_rust::TypeLifetimes> this_lifetimes;
if (lifetimes) {
this_lifetimes = lifetimes->this_lifetimes;
all_lifetimes.insert(this_lifetimes->begin(), this_lifetimes->end());
}
auto param_type = ConvertType(method_decl->getThisType(), this_lifetimes,
/*nullable=*/false);
if (!param_type.ok()) {
PushUnsupportedItem(function_decl, param_type.status().ToString(),
method_decl->getBeginLoc());
success = false;
} else {
params.push_back({*std::move(param_type), Identifier("__this")});
}
}
}
if (lifetimes) {
CHECK_EQ(lifetimes->param_lifetimes.size(), function_decl->getNumParams());
}
for (unsigned i = 0; i < function_decl->getNumParams(); ++i) {
const clang::ParmVarDecl* param = function_decl->getParamDecl(i);
std::optional<devtools_rust::TypeLifetimes> param_lifetimes;
if (lifetimes) {
param_lifetimes = lifetimes->param_lifetimes[i];
all_lifetimes.insert(param_lifetimes->begin(), param_lifetimes->end());
}
auto param_type = ConvertType(param->getType(), param_lifetimes);
if (!param_type.ok()) {
PushUnsupportedItem(
function_decl,
absl::Substitute("Parameter type '$0' is not supported",
param->getType().getAsString()),
param->getBeginLoc());
success = false;
continue;
}
if (const clang::RecordType* record_type =
llvm::dyn_cast<clang::RecordType>(param->getType())) {
if (clang::RecordDecl* record_decl =
llvm::dyn_cast<clang::RecordDecl>(record_type->getDecl())) {
// TODO(b/200067242): non-trivial_abi structs, when passed by value,
// have a different representation which needs special support. We
// currently do not support it.
if (!record_decl->canPassInRegisters()) {
PushUnsupportedItem(
function_decl,
absl::Substitute("Non-trivial_abi type '$0' is not "
"supported by value as a parameter",
param->getType().getAsString()),
param->getBeginLoc());
success = false;
}
}
}
std::optional<Identifier> param_name = GetTranslatedIdentifier(param);
CHECK(param_name.has_value()); // No known cases where the above can fail.
params.push_back({*param_type, *std::move(param_name)});
}
if (const clang::RecordType* record_return_type =
llvm::dyn_cast<clang::RecordType>(function_decl->getReturnType())) {
if (clang::RecordDecl* record_decl =
llvm::dyn_cast<clang::RecordDecl>(record_return_type->getDecl())) {
// TODO(b/200067242): non-trivial_abi structs, when passed by value,
// have a different representation which needs special support. We
// currently do not support it.
if (!record_decl->canPassInRegisters()) {
PushUnsupportedItem(
function_decl,
absl::Substitute("Non-trivial_abi type '$0' is not supported "
"by value as a return type",
function_decl->getReturnType().getAsString()),
function_decl->getReturnTypeSourceRange());
success = false;
}
}
}
std::optional<devtools_rust::TypeLifetimes> return_lifetimes;
if (lifetimes) {
return_lifetimes = lifetimes->return_lifetimes;
all_lifetimes.insert(return_lifetimes->begin(), return_lifetimes->end());
}
auto return_type =
ConvertType(function_decl->getReturnType(), return_lifetimes);
if (!return_type.ok()) {
PushUnsupportedItem(
function_decl,
absl::Substitute("Return type '$0' is not supported",
function_decl->getReturnType().getAsString()),
function_decl->getReturnTypeSourceRange());
success = false;
}
std::vector<Lifetime> lifetime_params;
for (devtools_rust::Lifetime lifetime : all_lifetimes) {
std::optional<llvm::StringRef> name =
lifetime_symbol_table.LookupLifetime(lifetime);
CHECK(name.has_value());
lifetime_params.push_back(
{.name = name->str(), .id = LifetimeId(lifetime.Id())});
}
std::sort(
lifetime_params.begin(), lifetime_params.end(),
[](const Lifetime& l1, const Lifetime& l2) { return l1.name < l2.name; });
std::optional<MemberFuncMetadata> member_func_metadata;
if (auto* method_decl = llvm::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
switch (method_decl->getAccess()) {
case clang::AS_public:
break;
case clang::AS_protected:
case clang::AS_private:
case clang::AS_none:
// No need for IR to include Func representing private methods.
// TODO(lukasza): Revisit this for protected methods.
return true;
}
std::optional<MemberFuncMetadata::InstanceMethodMetadata> instance_metadata;
if (method_decl->isInstance()) {
MemberFuncMetadata::ReferenceQualification reference;
switch (method_decl->getRefQualifier()) {
case clang::RQ_LValue:
reference = MemberFuncMetadata::kLValue;
break;
case clang::RQ_RValue:
reference = MemberFuncMetadata::kRValue;
break;
case clang::RQ_None:
reference = MemberFuncMetadata::kUnqualified;
break;
}
instance_metadata = MemberFuncMetadata::InstanceMethodMetadata{
.reference = reference,
.is_const = method_decl->isConst(),
.is_virtual = method_decl->isVirtual(),
};
}
member_func_metadata = MemberFuncMetadata{
.record_id = GenerateDeclId(method_decl->getParent()),
.instance_method_metadata = instance_metadata};
}
std::optional<UnqualifiedIdentifier> translated_name =
GetTranslatedName(function_decl);
if (success && translated_name.has_value()) {
ir_.items.push_back(Func{
.name = *translated_name,
.owning_target = GetOwningTarget(function_decl),
.doc_comment = GetComment(function_decl),
.mangled_name = GetMangledName(function_decl),
.return_type = *return_type,
.params = std::move(params),
.lifetime_params = std::move(lifetime_params),
.is_inline = function_decl->isInlined(),
.member_func_metadata = std::move(member_func_metadata),
.source_loc = ConvertSourceLocation(function_decl->getBeginLoc()),
});
}
return true;
}
BlazeLabel AstVisitor::GetOwningTarget(const clang::Decl* decl) const {
clang::SourceManager& source_manager = ctx_->getSourceManager();
llvm::StringRef filename = source_manager.getFilename(decl->getLocation());
if (filename.startswith("./")) filename = filename.substr(2);
auto target_iterator = headers_to_targets_.find(HeaderName(filename.str()));
if (target_iterator == headers_to_targets_.end()) {
// TODO(b/208377928): replace this hack with a
// CHECK(target_iterator != headers_to_targets_.end()) once we generate
// bindings for headers in Clang's resource dir.
return BlazeLabel("//:virtual_clang_resource_dir_target");
}
return target_iterator->second;
}
bool AstVisitor::IsFromCurrentTarget(const clang::Decl* decl) const {
return current_target_ == GetOwningTarget(decl);
}
bool AstVisitor::VisitRecordDecl(clang::RecordDecl* record_decl) {
const clang::DeclContext* decl_context = record_decl->getDeclContext();
if (decl_context) {
if (decl_context->isFunctionOrMethod()) {
return true;
}
if (decl_context->isRecord()) {
PushUnsupportedItem(record_decl, "Nested classes are not supported yet",
record_decl->getBeginLoc());
return true;
}
}
// Make sure the record has a definition that we'll be able to call
// ASTContext::getASTRecordLayout() on.
record_decl = record_decl->getDefinition();
if (!record_decl || record_decl->isInvalidDecl() ||
!record_decl->isCompleteDefinition()) {
return true;
}
clang::AccessSpecifier default_access = clang::AS_public;
bool is_final = true;
if (auto* cxx_record_decl =
clang::dyn_cast<clang::CXXRecordDecl>(record_decl)) {
if (cxx_record_decl->getDescribedClassTemplate() ||
clang::isa<clang::ClassTemplateSpecializationDecl>(record_decl)) {
PushUnsupportedItem(record_decl, "Class templates are not supported yet",
record_decl->getBeginLoc());
return true;
}
sema_.ForceDeclarationOfImplicitMembers(cxx_record_decl);
if (cxx_record_decl->isClass()) {
default_access = clang::AS_private;
}
is_final = cxx_record_decl->isEffectivelyFinal();
}
std::optional<Identifier> record_name = GetTranslatedIdentifier(record_decl);
if (!record_name.has_value()) {
return true;
}
// Provisionally assume that we know this RecordDecl so that we'll be able
// to import fields whose type contains the record itself.
known_type_decls_.insert(record_decl);
std::optional<std::vector<Field>> fields =
ImportFields(record_decl, default_access);
if (!fields.has_value()) {
// Importing a field failed, so note that we didn't import this RecordDecl
// after all.
known_type_decls_.erase(record_decl);
return true;
}
const clang::ASTRecordLayout& layout = ctx_->getASTRecordLayout(record_decl);
ir_.items.push_back(
Record{.identifier = *record_name,
.id = GenerateDeclId(record_decl),
.owning_target = GetOwningTarget(record_decl),
.doc_comment = GetComment(record_decl),
.fields = *std::move(fields),
.size = layout.getSize().getQuantity(),
.alignment = layout.getAlignment().getQuantity(),
.copy_constructor = GetCopyCtorSpecialMemberFunc(*record_decl),
.move_constructor = GetMoveCtorSpecialMemberFunc(*record_decl),
.destructor = GetDestructorSpecialMemberFunc(*record_decl),
.is_trivial_abi = record_decl->canPassInRegisters(),
.is_final = is_final});
return true;
}
bool AstVisitor::VisitTypedefNameDecl(
clang::TypedefNameDecl* typedef_name_decl) {
const clang::DeclContext* decl_context = typedef_name_decl->getDeclContext();
if (decl_context) {
if (decl_context->isFunctionOrMethod()) {
return true;
}
if (decl_context->isRecord()) {
PushUnsupportedItem(typedef_name_decl,
"Typedefs nested in classes are not supported yet",
typedef_name_decl->getBeginLoc());
return true;
}
}
clang::QualType type =
typedef_name_decl->getASTContext().getTypedefType(typedef_name_decl);
if (kWellKnownTypes.contains(type.getAsString())) {
return true;
}
std::optional<Identifier> identifier =
GetTranslatedIdentifier(typedef_name_decl);
if (!identifier.has_value()) {
// This should never happen.
LOG(FATAL) << "Couldn't get identifier for TypedefNameDecl";
return true;
}
absl::StatusOr<MappedType> underlying_type =
ConvertType(typedef_name_decl->getUnderlyingType());
if (underlying_type.ok()) {
known_type_decls_.insert(typedef_name_decl);
ir_.items.push_back(
TypeAlias{.identifier = *identifier,
.id = GenerateDeclId(typedef_name_decl),
.owning_target = GetOwningTarget(typedef_name_decl),
.underlying_type = *underlying_type});
} else {
PushUnsupportedItem(typedef_name_decl, underlying_type.status().ToString(),
typedef_name_decl->getBeginLoc());
}
return true;
}
std::optional<std::string> AstVisitor::GetComment(
const clang::Decl* decl) const {
// This does currently not distinguish between different types of comments.
// In general it is not possible in C++ to reliably only extract doc comments.
// This is going to be a heuristic that needs to be tuned over time.
clang::SourceManager& sm = ctx_->getSourceManager();
clang::RawComment* raw_comment = ctx_->getRawCommentForDeclNoCache(decl);
if (raw_comment == nullptr) {
return {};
} else {
return raw_comment->getFormattedText(sm, sm.getDiagnostics());
}
}
void AstVisitor::PushUnsupportedItem(const clang::Decl* decl,
std::string message,
clang::SourceLocation source_location) {
if (!IsFromCurrentTarget(decl)) return;
std::string name = "unnamed";
if (const auto* named_decl = llvm::dyn_cast<clang::NamedDecl>(decl)) {
name = named_decl->getQualifiedNameAsString();
}
ir_.items.push_back(UnsupportedItem{
.name = std::move(name),
.message = std::move(message),
.source_loc = ConvertSourceLocation(std::move(source_location))});
}
void AstVisitor::PushUnsupportedItem(const clang::Decl* decl,
std::string message,
clang::SourceRange source_range) {
PushUnsupportedItem(decl, message, source_range.getBegin());
}
SourceLoc AstVisitor::ConvertSourceLocation(clang::SourceLocation loc) const {
auto& sm = ctx_->getSourceManager();
clang::StringRef filename = sm.getFilename(loc);
if (filename.startswith("./")) {
filename = filename.substr(2);
}
return SourceLoc{.filename = filename.str(),
.line = sm.getSpellingLineNumber(loc),
.column = sm.getSpellingColumnNumber(loc)};
}
absl::StatusOr<MappedType> AstVisitor::ConvertType(
clang::QualType qual_type,
std::optional<devtools_rust::TypeLifetimes> lifetimes,
bool nullable) const {
std::optional<MappedType> type = std::nullopt;
// When converting the type to a string, don't include qualifiers -- we handle
// these separately.
std::string type_string = qual_type.getUnqualifiedType().getAsString();
if (auto iter = kWellKnownTypes.find(type_string);
iter != kWellKnownTypes.end()) {
type = MappedType::Simple(std::string(iter->second), type_string);
} else if (const auto* pointer_type =
qual_type->getAs<clang::PointerType>()) {
std::optional<LifetimeId> lifetime;
if (lifetimes.has_value()) {
CHECK(!lifetimes->empty());
lifetime = LifetimeId(lifetimes->back().Id());
lifetimes->pop_back();
}
auto pointee_type = ConvertType(pointer_type->getPointeeType(), lifetimes);
if (pointee_type.ok()) {
type = MappedType::PointerTo(*pointee_type, lifetime, nullable);
}
} else if (const auto* lvalue_ref_type =
qual_type->getAs<clang::LValueReferenceType>()) {
std::optional<LifetimeId> lifetime;
if (lifetimes.has_value()) {
CHECK(!lifetimes->empty());
lifetime = LifetimeId(lifetimes->back().Id());
lifetimes->pop_back();
}
auto pointee_type =
ConvertType(lvalue_ref_type->getPointeeType(), lifetimes);
if (pointee_type.ok()) {
type = MappedType::LValueReferenceTo(*pointee_type, lifetime);
}
} else if (const auto* builtin_type =
// Use getAsAdjusted instead of getAs so we don't desugar
// typedefs.
qual_type->getAsAdjusted<clang::BuiltinType>()) {
switch (builtin_type->getKind()) {
case clang::BuiltinType::Bool:
type = MappedType::Simple("bool", "bool");
break;
case clang::BuiltinType::Float:
type = MappedType::Simple("f32", "float");
break;
case clang::BuiltinType::Double:
type = MappedType::Simple("f64", "double");
break;
case clang::BuiltinType::Void:
type = MappedType::Void();
break;
default:
if (builtin_type->isIntegerType()) {
auto size = ctx_->getTypeSize(builtin_type);
if (size == 8 || size == 16 || size == 32 || size == 64) {
type = MappedType::Simple(
absl::Substitute(
"$0$1", builtin_type->isSignedInteger() ? 'i' : 'u', size),
type_string);
}
}
}
} else if (const auto *tag_type =
qual_type->getAsAdjusted<clang::TagType>()) {
clang::TagDecl* tag_decl = tag_type->getDecl();
if (known_type_decls_.contains(tag_decl)) {
if (std::optional<Identifier> id = GetTranslatedIdentifier(tag_decl)) {
std::string ident(id->Ident());
DeclId decl_id = GenerateDeclId(tag_decl);
type = MappedType::WithDeclIds(ident, decl_id, ident, decl_id);
}
}
} else if (const auto *typedef_type =
qual_type->getAsAdjusted<clang::TypedefType>()) {
clang::TypedefNameDecl* typedef_name_decl = typedef_type->getDecl();
if (known_type_decls_.contains(typedef_name_decl)) {
if (std::optional<Identifier> id =
GetTranslatedIdentifier(typedef_name_decl)) {
std::string ident(id->Ident());
DeclId decl_id = GenerateDeclId(typedef_name_decl);
type = MappedType::WithDeclIds(ident, decl_id, ident, decl_id);
}
}
}
if (!type.has_value()) {
absl::Status error = absl::UnimplementedError(
absl::Substitute("Unsupported type '$0'", type_string));
error.SetPayload(kTypeStatusPayloadUrl, absl::Cord(type_string));
return error;
}
// Add cv-qualification.
type->cc_type.is_const = qual_type.isConstQualified();
// Not doing volatile for now -- note that volatile pointers do not exist in
// Rust, though volatile reads/writes still do.
return *std::move(type);
}
std::optional<std::vector<Field>> AstVisitor::ImportFields(
clang::RecordDecl* record_decl, clang::AccessSpecifier default_access) {
std::vector<Field> fields;
const clang::ASTRecordLayout& layout = ctx_->getASTRecordLayout(record_decl);
for (const clang::FieldDecl* field_decl : record_decl->fields()) {
auto type = ConvertType(field_decl->getType());
if (!type.ok()) {
PushUnsupportedItem(record_decl,
absl::Substitute("Field type '$0' is not supported",
field_decl->getType().getAsString()),
field_decl->getBeginLoc());
return std::nullopt;
}
clang::AccessSpecifier access = field_decl->getAccess();
if (access == clang::AS_none) {
access = default_access;
}
std::optional<Identifier> field_name = GetTranslatedIdentifier(field_decl);
if (!field_name.has_value()) {
PushUnsupportedItem(
record_decl,
absl::Substitute("Cannot translate name for field '$0'",
field_decl->getNameAsString()),
field_decl->getBeginLoc());
return std::nullopt;
}
fields.push_back(
{.identifier = *std::move(field_name),
.doc_comment = GetComment(field_decl),
.type = *type,
.access = TranslateAccessSpecifier(access),
.offset = layout.getFieldOffset(field_decl->getFieldIndex())});
}
return fields;
}
std::string AstVisitor::GetMangledName(
const clang::NamedDecl* named_decl) const {
clang::GlobalDecl decl;
// There are only three named decl types that don't work with the GlobalDecl
// unary constructor: GPU kernels (which do not exist in standard C++, so we
// ignore), constructors, and destructors. GlobalDecl does not support
// constructors and destructors from the unary constructor because there is
// more than one global declaration for a given constructor or destructor!
//
// * (Ctor|Dtor)_Complete is a function which constructs / destroys the
// entire object. This is what we want. :)
// * Dtor_Deleting is a function which additionally calls operator delete.
// * (Ctor|Dtor)_Base is a function which constructs/destroys the object but
// NOT including virtual base class subobjects.
// * (Ctor|Dtor)_Comdat: I *believe* this is the identifier used to
// deduplicate inline functions, and is not callable.
// * Dtor_(Copying|Default)Closure: These only exist in the MSVC++ ABI,
// which we don't support for now. I don't know when they are used.
//
// It was hard to piece this together, so writing it down here to explain why
// we magically picked the *_Complete variants.
if (auto dtor = llvm::dyn_cast<clang::CXXDestructorDecl>(named_decl)) {
decl = clang::GlobalDecl(dtor, clang::CXXDtorType::Dtor_Complete);
} else if (auto ctor =
llvm::dyn_cast<clang::CXXConstructorDecl>(named_decl)) {
decl = clang::GlobalDecl(ctor, clang::CXXCtorType::Ctor_Complete);
} else {
decl = clang::GlobalDecl(named_decl);
}
std::string name;
llvm::raw_string_ostream stream(name);
mangler_->mangleName(decl, stream);
stream.flush();
return name;
}
std::optional<UnqualifiedIdentifier> AstVisitor::GetTranslatedName(
const clang::NamedDecl* named_decl) const {
switch (named_decl->getDeclName().getNameKind()) {
case clang::DeclarationName::Identifier: {
auto name = std::string(named_decl->getName());
if (name.empty()) {
if (const clang::ParmVarDecl* param_decl =
clang::dyn_cast<clang::ParmVarDecl>(named_decl)) {
int param_pos = param_decl->getFunctionScopeIndex();
return {Identifier(absl::StrCat("__param_", param_pos))};
}
// TODO(lukasza): Handle anonymous structs (probably this won't be an
// issue until nested types are handled - b/200067824).
return std::nullopt;
}
return {Identifier(std::move(name))};
}
case clang::DeclarationName::CXXConstructorName:
return {SpecialName::kConstructor};
case clang::DeclarationName::CXXDestructorName:
return {SpecialName::kDestructor};
default:
// To be implemented later: operators, conversion functions.
// There are also e.g. literal operators, deduction guides, etc., but
// we might not need to implement them at all. Full list at:
// https://clang.llvm.org/doxygen/classclang_1_1DeclarationName.html#a9ab322d434446b43379d39e41af5cbe3
return std::nullopt;
}
}
void AstVisitor::CommentManager::TraverseDecl(clang::Decl* decl) {
ctx_ = &decl->getASTContext();
// When we go to a new file we flush the comments from the previous file,
// because source locations won't be comparable by '<' any more.
clang::FileID file = ctx_->getSourceManager().getFileID(decl->getBeginLoc());
// For example, we hit an invalid FileID for virtual destructor definitions.
if (file.isInvalid()) {
return;
}
if (file != current_file_) {
FlushComments();
current_file_ = file;
LoadComments();
}
// Visit all comments from the current file up to the current decl.
clang::RawComment* decl_comment = ctx_->getRawCommentForDeclNoCache(decl);
while (next_comment_ != file_comments_.end() &&
(*next_comment_)->getBeginLoc() < decl->getBeginLoc()) {
// Skip the decl's doc comment, which will be emitted as part of the decl.
if (*next_comment_ != decl_comment) {
VisitTopLevelComment(*next_comment_);
}
++next_comment_;
}
// Skip comments that are within the decl, e.g., comments in the body of an
// inline function
// TODO(forster): We should retain floating comments within `Record`s
if (!clang::isa<clang::NamespaceDecl>(decl)) {
while (next_comment_ != file_comments_.end() &&
(*next_comment_)->getBeginLoc() < decl->getEndLoc()) {
++next_comment_;
}
}
}
void AstVisitor::CommentManager::LoadComments() {
auto comments = ctx_->Comments.getCommentsInFile(current_file_);
if (comments) {
for (auto [_, comment] : *comments) {
file_comments_.push_back(comment);
}
}
next_comment_ = file_comments_.begin();
}
void AstVisitor::CommentManager::FlushComments() {
while (next_comment_ != file_comments_.end()) {
VisitTopLevelComment(*next_comment_);
next_comment_++;
}
file_comments_.clear();
}
void AstVisitor::CommentManager::VisitTopLevelComment(
clang::RawComment* comment) {
clang::SourceManager& sm = ctx_->getSourceManager();
ir_.items.push_back(
Comment{.text = comment->getFormattedText(sm, sm.getDiagnostics())});
}
} // namespace rs_bindings_from_cc