| // 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/importer.h" |
| |
| #include <stdint.h> |
| |
| #include <cassert> |
| #include <iterator> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/base/no_destructor.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/log/check.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/cord.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/strings/substitute.h" |
| #include "common/status_macros.h" |
| #include "lifetime_annotations/type_lifetimes.h" |
| #include "rs_bindings_from_cc/ast_util.h" |
| #include "rs_bindings_from_cc/bazel_types.h" |
| #include "rs_bindings_from_cc/ir.h" |
| #include "rs_bindings_from_cc/recording_diagnostic_consumer.h" |
| #include "rs_bindings_from_cc/type_map.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclBase.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/DeclFriend.h" |
| #include "clang/AST/Mangle.h" |
| #include "clang/AST/RawCommentList.h" |
| #include "clang/AST/Type.h" |
| #include "clang/Basic/AttrKinds.h" |
| #include "clang/Basic/Diagnostic.h" |
| #include "clang/Basic/FileManager.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Basic/OperatorKinds.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Basic/Specifiers.h" |
| #include "clang/Sema/Sema.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/FormatVariadic.h" |
| #include "llvm/Support/Regex.h" |
| |
| namespace crubit { |
| namespace { |
| |
| constexpr absl::string_view kTypeStatusPayloadUrl = |
| "type.googleapis.com/devtools.rust.cc_interop.rs_binding_from_cc.type"; |
| |
| // Checks if the return value from `GetDeclItem` indicates that the import was |
| // successful. |
| absl::Status CheckImportStatus(const std::optional<IR::Item>& item) { |
| if (!item.has_value()) { |
| return absl::InvalidArgumentError("The import has been skipped"); |
| } |
| if (auto* unsupported = std::get_if<UnsupportedItem>(&*item)) { |
| std::vector<absl::string_view> messages; |
| messages.reserve(unsupported->errors.size()); |
| for (const auto& error : unsupported->errors) { |
| messages.push_back(error.message); |
| } |
| return absl::InvalidArgumentError(absl::StrJoin(messages, "\n\n")); |
| } |
| return absl::OkStatus(); |
| } |
| |
| // Returns true if the comment is boilerplate that should be filtered out. |
| bool IsFilteredComment(const clang::SourceManager& sm, |
| const clang::RawComment& comment) { |
| static absl::NoDestructor<llvm::Regex> kHeaderGuard("^// [A-Z_]*_H_ *$"); |
| if (kHeaderGuard->match(comment.getRawText(sm))) { |
| return true; |
| } |
| |
| // This one is a special case -- inside Crubit, we use a boilerplate license |
| // header at the top of all files. It's added to the top of the file of both |
| // the input and the output, and so we don't need to _repeat_ the version |
| // that originated in the input. |
| if (comment.getRawText(sm) == |
| "// Part of the Crubit project, under the Apache License v2.0 with " |
| "LLVM\n// Exceptions. See /LICENSE for license information.\n// " |
| "SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception") { |
| return true; |
| } |
| return false; |
| } |
| } // namespace |
| |
| namespace { |
| |
| // Converts clang::CallingConv enum [1] into an equivalent Rust Abi [2, 3, 4]. |
| // [1] |
| // https://github.com/llvm/llvm-project/blob/c6a3225bb03b6afc2b63fbf13db3c100406b32ce/clang/include/clang/Basic/Specifiers.h#L262-L283 |
| // [2] https://doc.rust-lang.org/reference/types/function-pointer.html |
| // [3] |
| // https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier |
| // [4] |
| // https://github.com/rust-lang/rust/blob/b27ccbc7e1e6a04d749e244a3c13f72ca38e80e7/compiler/rustc_target/src/spec/abi.rs#L49 |
| absl::StatusOr<absl::string_view> ConvertCcCallConvIntoRsAbi( |
| clang::CallingConv cc_call_conv) { |
| switch (cc_call_conv) { |
| case clang::CC_C: // __attribute__((cdecl)) |
| // https://doc.rust-lang.org/reference/items/external-blocks.html#abi says |
| // that: |
| // - `extern "C"` [...] whatever the default your C compiler supports. |
| // - `extern "cdecl"` -- The default for x86_32 C code. |
| // |
| // We don't support C++ exceptions and therefore we use "C" (rather than |
| // "C-unwind") - we have no need for unwinding across the FFI boundary - |
| // e.g. from C++ into Rust frames (or vice versa). |
| return "C"; |
| case clang::CC_X86FastCall: // __attribute__((fastcall)) |
| // https://doc.rust-lang.org/reference/items/external-blocks.html#abi says |
| // that the fastcall ABI -- corresponds to MSVC's __fastcall and GCC and |
| // clang's __attribute__((fastcall)). |
| return "fastcall"; |
| case clang::CC_X86VectorCall: // __attribute__((vectorcall)) |
| // https://doc.rust-lang.org/reference/items/external-blocks.html#abi says |
| // that the vectorcall ABI -- corresponds to MSVC's __vectorcall and |
| // clang's __attribute__((vectorcall)). |
| return "vectorcall"; |
| case clang::CC_X86ThisCall: // __attribute__((thiscall)) |
| // We don't support C++ exceptions and therefore we use "thiscall" (rather |
| // than "thiscall-unwind") - we have no need for unwinding across the FFI |
| // boundary - e.g. from C++ into Rust frames (or vice versa). |
| return "thiscall"; |
| case clang::CC_X86StdCall: // __attribute__((stdcall)) |
| // https://doc.rust-lang.org/reference/items/external-blocks.html#abi says |
| // extern "stdcall" -- The default for the Win32 API on x86_32. |
| // |
| // We don't support C++ exceptions and therefore we use "stdcall" (rather |
| // than "stdcall-unwind") - we have no need for unwinding across the FFI |
| // boundary - e.g. from C++ into Rust frames (or vice versa). |
| return "stdcall"; |
| case clang::CC_Win64: // __attribute__((ms_abi)) |
| // https://doc.rust-lang.org/reference/items/external-blocks.html#abi says |
| // extern "win64" -- The default for C code on x86_64 Windows. |
| return "win64"; |
| case clang::CC_AAPCS: // __attribute__((pcs("aapcs"))) |
| case clang::CC_AAPCS_VFP: // __attribute__((pcs("aapcs-vfp"))) |
| // TODO(lukasza): Should both map to "aapcs"? |
| break; |
| case clang::CC_X86_64SysV: // __attribute__((sysv_abi)) |
| // TODO(lukasza): Maybe this is "sysv64"? |
| break; |
| case clang::CC_X86Pascal: // __attribute__((pascal)) |
| case clang::CC_X86RegCall: // __attribute__((regcall)) |
| case clang::CC_IntelOclBicc: // __attribute__((intel_ocl_bicc)) |
| case clang::CC_SpirFunction: // default for OpenCL functions on SPIR target |
| case clang::CC_OpenCLKernel: // inferred for OpenCL kernels |
| case clang::CC_Swift: // __attribute__((swiftcall)) |
| case clang::CC_SwiftAsync: // __attribute__((swiftasynccall)) |
| case clang::CC_PreserveMost: // __attribute__((preserve_most)) |
| case clang::CC_PreserveAll: // __attribute__((preserve_all)) |
| case clang::CC_AArch64VectorCall: // __attribute__((aarch64_vector_pcs)) |
| // TODO(hlopko): Uncomment once we integrate the upstream change that |
| // introduced it: |
| // case clang::CC_AArch64SVEPCS: __attribute__((aarch64_sve_pcs)) |
| |
| // These don't seem to have any Rust equivalents. |
| break; |
| default: |
| break; |
| } |
| return absl::UnimplementedError( |
| absl::StrCat("Unsupported calling convention: ", |
| absl::string_view( |
| clang::FunctionType::getNameForCallConv(cc_call_conv)))); |
| } |
| |
| } // namespace |
| |
| // Multiple IR items can be associated with the same source location (e.g. the |
| // implicitly defined constructors and assignment operators). To produce |
| // deterministic output, we order such items based on GetDeclOrder. The order |
| // is somewhat arbitrary, but we still try to make it aesthetically pleasing |
| // (e.g. constructors go before assignment operators; default constructor goes |
| // first, etc.). |
| static int GetDeclOrder(const clang::Decl* decl) { |
| if (clang::isa<clang::RecordDecl>(decl)) { |
| return decl->getDeclContext()->isRecord() ? 101 : 100; |
| } |
| |
| if (auto* ctor = clang::dyn_cast<clang::CXXConstructorDecl>(decl)) { |
| return ctor->isDefaultConstructor() ? 202 |
| : ctor->isCopyConstructor() ? 203 |
| : ctor->isMoveConstructor() ? 204 |
| : 299; |
| } |
| |
| if (clang::isa<clang::CXXDestructorDecl>(decl)) { |
| return 306; |
| } |
| |
| if (auto* method = clang::dyn_cast<clang::CXXMethodDecl>(decl)) { |
| return method->isCopyAssignmentOperator() ? 401 |
| : method->isMoveAssignmentOperator() ? 402 |
| : 499; |
| } |
| |
| return 999; |
| } |
| |
| class Importer::SourceOrderKey { |
| public: |
| explicit SourceOrderKey(clang::SourceRange source_range, int decl_order = 0, |
| std::string name = "") |
| : source_range_(source_range), decl_order_(decl_order), name_(name) {} |
| |
| SourceOrderKey(const SourceOrderKey&) = default; |
| SourceOrderKey& operator=(const SourceOrderKey&) = default; |
| |
| bool isBefore(const SourceOrderKey& other, |
| const clang::SourceManager& sm) const { |
| if (!source_range_.isValid() || !other.source_range_.isValid()) { |
| if (source_range_.isValid() != other.source_range_.isValid()) |
| return !source_range_.isValid() && other.source_range_.isValid(); |
| } else { |
| if (source_range_.getBegin() != other.source_range_.getBegin()) { |
| return sm.isBeforeInTranslationUnit(source_range_.getBegin(), |
| other.source_range_.getBegin()); |
| } |
| if (source_range_.getEnd() != other.source_range_.getEnd()) { |
| return sm.isBeforeInTranslationUnit(source_range_.getEnd(), |
| other.source_range_.getEnd()); |
| } |
| } |
| |
| if (decl_order_ < other.decl_order_) { |
| return true; |
| } else if (decl_order_ > other.decl_order_) { |
| return false; |
| } |
| return name_ < other.name_; |
| } |
| |
| private: |
| clang::SourceRange source_range_; |
| int decl_order_; |
| std::string name_; |
| }; |
| |
| Importer::SourceOrderKey Importer::GetSourceOrderKey( |
| const clang::Decl* decl) const { |
| return SourceOrderKey(decl->getSourceRange(), GetDeclOrder(decl), |
| GetNameForSourceOrder(decl)); |
| } |
| |
| Importer::SourceOrderKey Importer::GetSourceOrderKey( |
| const clang::RawComment* comment) const { |
| return SourceOrderKey(comment->getSourceRange()); |
| } |
| |
| class Importer::SourceLocationComparator { |
| public: |
| bool operator()(const clang::SourceLocation& a, |
| const clang::SourceLocation& b) const { |
| return b.isValid() && a.isValid() && sm_.isBeforeInTranslationUnit(a, b); |
| } |
| bool operator()(const clang::RawComment* a, |
| const clang::SourceLocation& b) const { |
| return this->operator()(a->getBeginLoc(), b); |
| } |
| bool operator()(const clang::SourceLocation& a, |
| const clang::RawComment* b) const { |
| return this->operator()(a, b->getBeginLoc()); |
| } |
| bool operator()(const clang::RawComment* a, |
| const clang::RawComment* b) const { |
| return this->operator()(a->getBeginLoc(), b->getBeginLoc()); |
| } |
| |
| using OrderedItemId = std::pair<SourceOrderKey, ItemId>; |
| using OrderedItem = std::pair<SourceOrderKey, IR::Item>; |
| |
| template <typename OrderedItemOrId> |
| bool operator()(const OrderedItemOrId& a, const OrderedItemOrId& b) const { |
| auto a_source_order = a.first; |
| auto b_source_order = b.first; |
| return a_source_order.isBefore(b_source_order, sm_); |
| } |
| explicit SourceLocationComparator(const clang::SourceManager& sm) : sm_(sm) {} |
| |
| private: |
| const clang::SourceManager& sm_; |
| }; |
| |
| std::vector<clang::Decl*> Importer::GetCanonicalChildren( |
| const clang::DeclContext* decl_context) const { |
| std::vector<clang::Decl*> result; |
| for (clang::Decl* decl : decl_context->decls()) { |
| if (const auto* linkage_spec_decl = |
| llvm::dyn_cast<clang::LinkageSpecDecl>(decl)) { |
| llvm::move(GetCanonicalChildren(linkage_spec_decl), |
| std::back_inserter(result)); |
| continue; |
| } |
| |
| // `CXXRecordDeclImporter::Import` supports class template specializations |
| // but such import should only be triggered when |
| // `Importer::ConvertTemplateSpecializationType` is called (which means that |
| // the specialization is actually used in an explicit instantiation via |
| // `cc_template!` macro, in a type alias, or as a parameter type of a |
| // function, etc.). |
| if (clang::isa<clang::ClassTemplateSpecializationDecl>(decl)) continue; |
| |
| // In general we only import (and include as children) canonical decls. |
| // Namespaces are exempted to ensure that we process every one of |
| // (potential) multiple namespace blocks with the same name. |
| // CXXRecordDecls are exempted because we use the _definition_, not |
| // the "canonical" decl (which may be a forward declaration). |
| if (clang::Decl* canonical_decl = CanonicalizeDecl(decl); |
| canonical_decl == decl) { |
| result.push_back(decl); |
| } |
| } |
| return result; |
| } |
| |
| const clang::Decl* Importer::CanonicalizeDecl(const clang::Decl* decl) const { |
| if (auto* namespace_decl = llvm::dyn_cast<clang::NamespaceDecl>(decl)) { |
| return namespace_decl; |
| } |
| if (auto* cxx_record_decl = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) { |
| if (cxx_record_decl->isInjectedClassName()) { |
| return nullptr; |
| } |
| auto owning_target = GetOwningTarget(decl); |
| const auto& source_manager = sema_.getSourceManager(); |
| clang::Decl* canonical = nullptr; |
| for (auto iter = decl->redecls_begin(); iter != decl->redecls_end(); |
| ++iter) { |
| auto* redecl = llvm::dyn_cast<clang::CXXRecordDecl>(*iter); |
| CHECK(redecl); |
| if (redecl->isInjectedClassName()) { |
| continue; |
| } |
| if (GetOwningTarget(redecl) != owning_target) { |
| continue; |
| } |
| if (redecl->isThisDeclarationADefinition()) { |
| canonical = redecl; |
| break; // Multiple definitions are not allowed for record types. |
| } |
| if (!canonical) { |
| canonical = redecl; |
| continue; |
| } |
| if (source_manager.isBeforeInTranslationUnit( |
| redecl->getSourceRange().getBegin(), |
| canonical->getSourceRange().getBegin())) { |
| canonical = redecl; |
| } |
| } |
| CHECK(canonical != nullptr); |
| return canonical; |
| } |
| return decl->getCanonicalDecl(); |
| } |
| |
| clang::Decl* Importer::CanonicalizeDecl(clang::Decl* decl) const { |
| const clang::Decl* decl_const = const_cast<clang::Decl*>(decl); |
| const clang::Decl* ret = CanonicalizeDecl(decl_const); |
| return const_cast<clang::Decl*>(ret); |
| } |
| |
| ItemId Importer::GenerateItemId(const clang::Decl* decl) const { |
| const clang::Decl* canonicalized = CanonicalizeDecl(decl); |
| return ItemId(reinterpret_cast<uintptr_t>(canonicalized)); |
| } |
| |
| bool Importer::IsUnsupportedAndAlien(ItemId item_id) const { |
| auto it = import_cache_.find(reinterpret_cast<clang::Decl*>(item_id.value())); |
| return it != import_cache_.end() && it->second.has_value() && |
| std::holds_alternative<UnsupportedItem>(*it->second) && |
| !IsFromCurrentTarget(it->first); |
| } |
| |
| ItemId Importer::GenerateItemId(const clang::RawComment* comment) const { |
| return ItemId(reinterpret_cast<uintptr_t>(comment)); |
| } |
| |
| absl::StatusOr<std::optional<ItemId>> Importer::GetEnclosingItemId( |
| clang::Decl* decl) { |
| for (clang::DeclContext* decl_context = decl->getDeclContext();; |
| decl_context = decl_context->getParent()) { |
| if (decl_context->isTranslationUnit()) { |
| return std::nullopt; |
| } |
| // Class template specializations are always emitted in the top-level |
| // namespace. See also Importer::GetOrderedItemIdsOfTemplateInstantiations. |
| if (clang::isa<clang::ClassTemplateSpecializationDecl>(decl)) |
| return std::nullopt; |
| |
| if (decl_context->isFunctionOrMethod()) { |
| return std::nullopt; |
| } |
| if (auto* record_decl = clang::dyn_cast<clang::RecordDecl>(decl_context)) { |
| if (!EnsureSuccessfullyImported(record_decl)) { |
| return absl::InvalidArgumentError("Couldn't import the parent"); |
| } |
| return GenerateItemId(record_decl); |
| } |
| if (auto* namespace_decl = |
| clang::dyn_cast<clang::NamespaceDecl>(decl_context)) { |
| return GenerateItemId(namespace_decl); |
| } |
| } |
| } |
| |
| std::vector<ItemId> Importer::GetItemIdsInSourceOrder( |
| clang::Decl* parent_decl) { |
| clang::SourceManager& sm = ctx_.getSourceManager(); |
| std::vector<SourceLocationComparator::OrderedItemId> items; |
| auto compare_locations = SourceLocationComparator(sm); |
| |
| // We are only interested in comments within this decl context. |
| std::vector<const clang::RawComment*> comments_in_range( |
| llvm::lower_bound(comments_, parent_decl->getBeginLoc(), |
| compare_locations), |
| llvm::upper_bound(comments_, parent_decl->getEndLoc(), |
| compare_locations)); |
| |
| std::map<clang::SourceLocation, const clang::RawComment*, |
| SourceLocationComparator> |
| ordered_comments(compare_locations); |
| for (auto& comment : comments_in_range) { |
| if (IsFilteredComment(sm, *comment)) continue; |
| ordered_comments.insert({comment->getBeginLoc(), comment}); |
| } |
| |
| absl::flat_hash_set<ItemId> visited_item_ids; |
| |
| auto* decl_context = clang::cast<clang::DeclContext>(parent_decl); |
| for (auto decl : GetCanonicalChildren(decl_context)) { |
| auto item = GetDeclItem(decl); |
| // We generated IR for top level items coming from different targets, |
| // however we shouldn't generate bindings for them, so we don't add them |
| // to ir.top_level_item_ids. |
| if (decl_context->isTranslationUnit() && !IsFromCurrentTarget(decl)) { |
| continue; |
| } |
| // Only add item ids for decls that can be successfully imported. |
| if (item.has_value()) { |
| auto item_id = GenerateItemId(decl); |
| // TODO(rosica): Drop this check when we start importing also other |
| // redecls, not just the canonical |
| if (visited_item_ids.find(item_id) == visited_item_ids.end()) { |
| visited_item_ids.insert(item_id); |
| items.push_back({GetSourceOrderKey(decl), item_id}); |
| } |
| } |
| |
| // We remove comments attached to a child decl or that are within a child |
| // decl. |
| if (auto raw_comment = ctx_.getRawCommentForDeclNoCache(decl)) { |
| ordered_comments.erase(raw_comment->getBeginLoc()); |
| } |
| ordered_comments.erase(ordered_comments.lower_bound(decl->getBeginLoc()), |
| ordered_comments.upper_bound(decl->getEndLoc())); |
| } |
| |
| for (auto& [_, comment] : ordered_comments) { |
| items.push_back({GetSourceOrderKey(comment), GenerateItemId(comment)}); |
| } |
| llvm::sort(items, compare_locations); |
| |
| std::vector<ItemId> ordered_item_ids; |
| ordered_item_ids.reserve(items.size()); |
| for (auto& ordered_item : items) { |
| ordered_item_ids.push_back(ordered_item.second); |
| } |
| return ordered_item_ids; |
| } |
| |
| std::vector<ItemId> Importer::GetOrderedItemIdsOfTemplateInstantiations() |
| const { |
| std::vector<SourceLocationComparator::OrderedItemId> items; |
| items.reserve(class_template_instantiations_.size()); |
| for (const auto* decl : class_template_instantiations_) { |
| items.push_back({GetSourceOrderKey(decl), GenerateItemId(decl)}); |
| } |
| |
| clang::SourceManager& sm = ctx_.getSourceManager(); |
| auto compare_locations = SourceLocationComparator(sm); |
| llvm::sort(items, compare_locations); |
| |
| std::vector<ItemId> ordered_item_ids; |
| ordered_item_ids.reserve(items.size()); |
| for (const auto& ordered_item : items) { |
| ordered_item_ids.push_back(ordered_item.second); |
| } |
| return ordered_item_ids; |
| } |
| |
| void Importer::ImportFreeComments() { |
| clang::SourceManager& sm = ctx_.getSourceManager(); |
| for (const auto& header : invocation_.public_headers_) { |
| if (auto file = sm.getFileManager().getFileRef(header.IncludePath())) { |
| if (auto comments_in_file = ctx_.Comments.getCommentsInFile( |
| sm.getOrCreateFileID(*file, clang::SrcMgr::C_User))) { |
| for (const auto& [_, comment] : *comments_in_file) { |
| comments_.push_back(comment); |
| } |
| } |
| } |
| } |
| llvm::sort(comments_, SourceLocationComparator(sm)); |
| } |
| |
| void Importer::Import(clang::TranslationUnitDecl* translation_unit_decl) { |
| ImportFreeComments(); |
| clang::SourceManager& sm = ctx_.getSourceManager(); |
| std::vector<SourceLocationComparator::OrderedItem> ordered_items; |
| |
| ordered_items.reserve(comments_.size()); |
| for (auto& comment : comments_) { |
| ordered_items.push_back( |
| {GetSourceOrderKey(comment), |
| Comment{.text = comment->getFormattedText(sm, sm.getDiagnostics()), |
| .id = GenerateItemId(comment)}}); |
| } |
| |
| ImportDeclsFromDeclContext(translation_unit_decl); |
| for (const auto& [decl, item] : import_cache_) { |
| if (!item.has_value() || IsUnsupportedAndAlien(GenerateItemId(decl))) { |
| continue; |
| } |
| ordered_items.push_back({GetSourceOrderKey(decl), *item}); |
| } |
| |
| llvm::sort(ordered_items, SourceLocationComparator(sm)); |
| |
| invocation_.ir_.items.reserve(ordered_items.size()); |
| for (auto& ordered_item : ordered_items) { |
| invocation_.ir_.items.push_back(ordered_item.second); |
| } |
| invocation_.ir_.top_level_item_ids = |
| GetItemIdsInSourceOrder(translation_unit_decl); |
| |
| // TODO(b/257302656): Consider placing the generated template instantiations |
| // into a separate namespace (maybe `crubit::instantiated_templates` ?). |
| llvm::copy(GetOrderedItemIdsOfTemplateInstantiations(), |
| std::back_inserter(invocation_.ir_.top_level_item_ids)); |
| } |
| |
| void Importer::ImportDeclsFromDeclContext( |
| const clang::DeclContext* decl_context) { |
| for (auto decl : GetCanonicalChildren(decl_context)) { |
| GetDeclItem(decl); |
| } |
| } |
| |
| std::optional<IR::Item> Importer::GetDeclItem(clang::Decl* decl) { |
| // TODO(jeanpierreda): Move `decl->getCanonicalDecl()` from callers into here. |
| if (auto it = import_cache_.find(decl); it != import_cache_.end()) { |
| return it->second; |
| } |
| // Here, we need to be careful. Recursive imports break cycles as follows: |
| // an item which may, in the process of being imported, then import itself, |
| // will mark itself as being successfully imported in the future via |
| // `MarkAsSuccessfullyImported()` during its own import process. Then later |
| // attempts to import it will, instead of trying to import it again (causing |
| // an infinite loop), short-circuit and return a null item at that time. |
| // |
| // This means that import_cache_ can change *during the call to ImportDecl*, |
| // and in particular, because `GetDeclItem` caches, and because recursive |
| // calls return null, it might specifically have been changed to have a |
| // null entry for the decl we are currently importing. |
| // |
| // For example, consider the following type: |
| // |
| // ```c++ |
| // struct Foo{ Foo* x; } |
| // ``` |
| // |
| // 1. First, we call `GetDeclItem(mystruct)` |
| // 2. If importing `x` itself attempts an import of `Foo*`, then that would |
| // call `GetDeclItem(mystruct)` inside of an existing call to |
| // `GetDeclItem(mystruct)`. |
| // 3. The nested call returns early, returning null (because this is a |
| // cyclic invocation), and `GetDeclItem` **caches the null entry**. |
| // 4. finally, the original `GetDeclItem` call finishes its call to |
| // `ImportDecl`. It must now overwrite the nulled cache entry from the |
| // earlier import to instead use the real entry. |
| // |
| // TODO(jeanpierreda): find and eliminate all re-entrant imports, and replace with |
| // a CHECK(inserted). |
| |
| // Note: insert_or_assign, not insert, in case a record, so as to overwrite |
| // any null entries introduced by cycles. |
| |
| std::optional<IR::Item> result = ImportDecl(decl); |
| auto [it, inserted] = import_cache_.try_emplace(decl, result); |
| if (!inserted) { |
| // TODO(jeanpierreda): Fix and promote to CHECK. |
| // At least one cycle occurs with Typedef, where a typedef will import |
| // itself during its own import. This isn't an infinite loop, because the |
| // recursive cycle gets broken between the two by CXXRecordDecl, but the |
| // result is that we get this typedef inserted while we were attempting to |
| // insert it. |
| // |
| // Alternatively, maybe it's sufficient to check that they're _equal_. |
| // It's not a bug at all to import it twice if it has no effect. |
| LOG_IF(INFO, !it->second.has_value()) |
| << "re-entrant import discovered, where the re-entrant import had a " |
| "non-null value." |
| << "\n trying to import a " << decl->getDeclKindName() |
| << "\n present entry: " << ItemToString(it->second) |
| << "\n was going to be inserted: " << ItemToString(result); |
| it->second = result; |
| } |
| if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) { |
| // TODO(forster): Should we even visit the nested decl if we couldn't |
| // import the parent? For now we have tests that check that we generate |
| // error messages for those decls, so we're visiting. |
| ImportDeclsFromDeclContext(record_decl); |
| } |
| |
| // Logic for `ClassTemplateSpecializationDecl`: insert them into |
| // `class_template_instantiations_`, so that they will get included in |
| // IR::top_level_item_ids. |
| // Note: The 'gating' logic here needs to be consistent with the 'gating' |
| // logic in `GetItemIdsInSourceOrder`, or else the class template |
| // instantiation decl ID is inserted into the IR::top_level_item_ids but does |
| // not have its corresponding IR item, resulting in lookup failures (crashes) |
| // when generating bindings. |
| if (result.has_value()) { |
| if (auto* specialization_decl = |
| llvm::dyn_cast<clang::ClassTemplateSpecializationDecl>(decl); |
| specialization_decl && IsFromCurrentTarget(specialization_decl)) { |
| class_template_instantiations_.insert(specialization_decl); |
| } |
| } |
| return result; |
| } |
| |
| /// Returns true if a decl is inside a private section, or is inside a |
| /// RecordDecl which is IsTransitivelyInPrivate. |
| bool IsTransitivelyInPrivate(clang::Decl* decl_to_check) { |
| while (true) { |
| auto* parent = |
| llvm::dyn_cast<clang::CXXRecordDecl>(decl_to_check->getDeclContext()); |
| if (parent == nullptr) { |
| return false; |
| } |
| switch (decl_to_check->getAccess()) { |
| case clang::AccessSpecifier::AS_public: |
| break; |
| case clang::AccessSpecifier::AS_none: |
| if (!parent->isClass()) { |
| break; |
| } |
| [[fallthrough]]; |
| case clang::AccessSpecifier::AS_private: |
| case clang::AccessSpecifier::AS_protected: |
| return true; |
| } |
| |
| decl_to_check = parent; |
| } |
| } |
| |
| std::optional<IR::Item> Importer::ImportDecl(clang::Decl* decl) { |
| if (IsTransitivelyInPrivate(decl)) return std::nullopt; |
| for (auto& importer : decl_importers_) { |
| std::optional<IR::Item> result = importer->ImportDecl(decl); |
| if (result.has_value()) { |
| return result; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<IR::Item> Importer::GetImportedItem( |
| const clang::Decl* decl) const { |
| auto it = import_cache_.find(decl); |
| if (it != import_cache_.end()) { |
| return it->second; |
| } |
| return std::nullopt; |
| } |
| |
| BazelLabel Importer::GetOwningTarget(const clang::Decl* decl) const { |
| // Template instantiations need to be generated in the target that triggered |
| // the instantiation (not in the target where the template is defined). |
| if (IsFullClassTemplateSpecializationOrChild(decl)) { |
| return invocation_.target_; |
| } |
| |
| clang::SourceManager& source_manager = ctx_.getSourceManager(); |
| auto source_location = decl->getLocation(); |
| |
| // If the header this decl comes from is not associated with a target we |
| // consider it a textual header. In that case we go up the include stack |
| // until we find a header that has an owning target. |
| |
| while (source_location.isValid()) { |
| if (source_location.isMacroID()) { |
| source_location = source_manager.getExpansionLoc(source_location); |
| } |
| auto id = source_manager.getFileID(source_location); |
| std::optional<llvm::StringRef> filename = |
| source_manager.getNonBuiltinFilenameForID(id); |
| if (!filename) { |
| return BazelLabel("//:_nothing_should_depend_on_private_builtin_hdrs"); |
| } |
| if (filename->starts_with("./")) { |
| filename = filename->substr(2); |
| } |
| |
| if (auto target = invocation_.header_target(HeaderName(filename->str()))) { |
| return *target; |
| } |
| source_location = source_manager.getIncludeLoc(id); |
| } |
| |
| return BazelLabel("//:virtual_clang_resource_dir_target"); |
| } |
| |
| bool Importer::IsFromCurrentTarget(const clang::Decl* decl) const { |
| return invocation_.target_ == GetOwningTarget(decl); |
| } |
| |
| IR::Item Importer::ImportUnsupportedItem(const clang::Decl* decl, |
| FormattedError error) { |
| std::string name = "unnamed"; |
| if (const auto* named_decl = clang::dyn_cast<clang::NamedDecl>(decl)) { |
| name = named_decl->getQualifiedNameAsString(); |
| } |
| std::string source_loc = ConvertSourceLocation(decl->getBeginLoc()); |
| return UnsupportedItem{.name = name, |
| .errors = {error}, |
| .source_loc = source_loc, |
| .id = GenerateItemId(decl)}; |
| } |
| |
| IR::Item Importer::ImportUnsupportedItem(const clang::Decl* decl, |
| std::vector<FormattedError> errors) { |
| std::string name = "unnamed"; |
| if (const auto* named_decl = clang::dyn_cast<clang::NamedDecl>(decl)) { |
| name = named_decl->getQualifiedNameAsString(); |
| } |
| std::string source_loc = ConvertSourceLocation(decl->getBeginLoc()); |
| return UnsupportedItem{.name = name, |
| .errors = std::move(errors), |
| .source_loc = source_loc, |
| .id = GenerateItemId(decl)}; |
| } |
| |
| IR::Item Importer::ImportUnsupportedItem(const clang::Decl* decl, |
| std::string message) { |
| return ImportUnsupportedItem(decl, FormattedError{.message = message}); |
| } |
| |
| IR::Item Importer::ImportUnsupportedItem(const clang::Decl* decl, |
| std::set<std::string> messages) { |
| std::vector<FormattedError> errors; |
| errors.reserve(messages.size()); |
| for (std::string message : messages) { |
| errors.push_back(FormattedError{.message = std::move(message)}); |
| } |
| return ImportUnsupportedItem(decl, errors); |
| } |
| |
| static bool ShouldKeepCommentLine(absl::string_view line) { |
| // Based on https://clang.llvm.org/extra/clang-tidy/: |
| llvm::Regex patterns_to_ignore( |
| "^[[:space:]/]*" // Whitespace, or extra // |
| "(NOLINT|NOLINTNEXTLINE|NOLINTBEGIN|NOLINTEND)" |
| "(\\([^)[:space:]]*\\)?)?" // Optional (...) |
| "[[:space:]]*$"); // Whitespace |
| return !patterns_to_ignore.match(line); |
| } |
| |
| std::optional<std::string> Importer::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 {}; |
| } |
| |
| std::string raw_comment_text = |
| raw_comment->getFormattedText(sm, sm.getDiagnostics()); |
| std::string cleaned_comment_text = absl::StrJoin( |
| absl::StrSplit(raw_comment_text, '\n', ShouldKeepCommentLine), "\n"); |
| if (cleaned_comment_text.empty()) return {}; |
| return cleaned_comment_text; |
| } |
| |
| std::string Importer::ConvertSourceLocation(clang::SourceLocation loc) const { |
| auto& sm = ctx_.getSourceManager(); |
| // For macros: https://clang.llvm.org/doxygen/SourceManager_8h.html: |
| // Spelling location: where the macro is originally defined. |
| // Expansion location: where the macro is expanded. |
| const clang::SourceLocation& spelling_loc = sm.getSpellingLoc(loc); |
| // TODO(b/261185414): The "google3" prefix should probably come from a command |
| // line argument. |
| // TODO(b/261185414): Consider linking to the symbol instead of to the line |
| // number to avoid wrong links while generated files have not caught up. |
| constexpr absl::string_view kGeneratedFrom = "Generated from"; |
| constexpr absl::string_view kExpandedAt = "Expanded at"; |
| constexpr auto kSourceLocationFunc = |
| [](absl::string_view origin, absl::string_view filename, uint32_t line) { |
| return absl::Substitute("$0: google3/$1;l=$2", origin, filename, line); |
| }; |
| constexpr absl::string_view kSourceLocUnknown = "<unknown location>"; |
| std::string spelling_loc_str; |
| if (absl::string_view spelling_filename = sm.getFilename(spelling_loc); |
| spelling_filename.empty()) { |
| spelling_loc_str = kSourceLocUnknown; |
| } else { |
| uint32_t spelling_line = sm.getSpellingLineNumber(loc); |
| if (absl::StartsWith(spelling_filename, "./")) { |
| spelling_filename = spelling_filename.substr(2); |
| } |
| spelling_loc_str = |
| kSourceLocationFunc(kGeneratedFrom, spelling_filename, spelling_line); |
| } |
| if (!loc.isMacroID()) { |
| return spelling_loc_str; |
| } |
| const clang::SourceLocation& expansion_loc = sm.getExpansionLoc(loc); |
| std::string expansion_loc_str; |
| if (absl::string_view expansion_filename = sm.getFilename(expansion_loc); |
| expansion_filename.empty()) { |
| expansion_loc_str = kSourceLocUnknown; |
| } else { |
| uint32_t expansion_line = sm.getExpansionLineNumber(loc); |
| if (absl::StartsWith(expansion_filename, "./")) { |
| expansion_filename = expansion_filename.substr(2); |
| } |
| expansion_loc_str = |
| kSourceLocationFunc(kExpandedAt, expansion_filename, expansion_line); |
| } |
| return absl::StrCat(spelling_loc_str, "\n", expansion_loc_str); |
| } |
| |
| absl::StatusOr<MappedType> Importer::ConvertTemplateSpecializationType( |
| const clang::TemplateSpecializationType* type) { |
| // Qualifiers are handled separately in TypeMapper::ConvertQualType(). |
| std::string type_string = clang::QualType(type, 0).getAsString(); |
| |
| auto* specialization_decl = |
| clang::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>( |
| type->getAsCXXRecordDecl()); |
| if (!specialization_decl) { |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Template specialization '$0' without an associated record decl " |
| "is not supported.", |
| type_string)); |
| } |
| |
| if (HasBeenAlreadySuccessfullyImported(specialization_decl)) |
| return ConvertTypeDecl(specialization_decl); |
| |
| // `Sema::isCompleteType` will try to instantiate the class template as a |
| // side-effect and we rely on this here. `decl->getDefinition()` can |
| // return nullptr before the call to sema and return its definition |
| // afterwards. |
| // Note: Here we instantiate class template specialization eagerly: its |
| // usages in headers may not require the class template specialization to be |
| // instantiated (and hence it may not be instantiable), but we attempt |
| // instantiation here. So we may attempt non-instantiable template, which |
| // would cause the diagnostic stream to contain error, which would case |
| // clang::tooling::runToolOnCodeWithArgs to return an error status. To avoid |
| // erroring out, we temporarily use our own implementation of |
| // DiagnosticConsumer here. |
| crubit::RecordingDiagnosticConsumer diagnostic_recorder = |
| crubit::RecordDiagnostics(sema_.getDiagnostics(), [&] { |
| // Attempt to instantiate. |
| (void)sema_.isCompleteType(specialization_decl->getLocation(), |
| ctx_.getRecordType(specialization_decl)); |
| }); |
| if (diagnostic_recorder.getNumErrors() != 0) { |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Failed to complete template specialization type $0: Diagnostics " |
| "emitted:\n$1", |
| type_string, diagnostic_recorder.ConcatenatedDiagnostics())); |
| } |
| |
| // TODO(lukasza): Limit specialization depth? (e.g. using |
| // `isSpecializationDepthGreaterThan` from earlier prototypes). |
| |
| absl::Status import_status = |
| CheckImportStatus(GetDeclItem(specialization_decl)); |
| if (!import_status.ok()) { |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Failed to create bindings for template specialization type $0: $1", |
| type_string, import_status.message())); |
| } |
| |
| return ConvertTypeDecl(specialization_decl); |
| } |
| |
| absl::StatusOr<MappedType> Importer::ConvertTypeDecl(clang::NamedDecl* decl) { |
| if (!EnsureSuccessfullyImported(decl)) { |
| return absl::NotFoundError(absl::Substitute( |
| "No generated bindings found for '$0'", decl->getNameAsString())); |
| } |
| |
| ItemId decl_id = GenerateItemId(decl); |
| return MappedType::WithDeclId(decl_id); |
| } |
| |
| static bool IsSameCanonicalUnqualifiedType(clang::QualType type1, |
| clang::QualType type2) { |
| type1 = type1.getCanonicalType().getUnqualifiedType(); |
| type2 = type2.getCanonicalType().getUnqualifiedType(); |
| |
| // `DeducedType::getDeducedType()` can return null, in which case we don't |
| // have a more canonical representation. If this happens, optimistically |
| // assume the types are equal. |
| if (clang::isa<clang::DeducedType>(type1) || |
| clang::isa<clang::DeducedType>(type2)) |
| return true; |
| |
| return type1 == type2; |
| } |
| |
| absl::StatusOr<MappedType> Importer::ConvertType( |
| const clang::Type* type, |
| const clang::tidy::lifetimes::ValueLifetimes* lifetimes, |
| std::optional<clang::RefQualifierKind> ref_qualifier_kind, bool nullable) { |
| absl::StatusOr<MappedType> mapped_type = |
| ConvertUnattributedType(type, lifetimes, ref_qualifier_kind, nullable); |
| if (mapped_type.ok()) { |
| mapped_type->rs_type.unknown_attr = |
| CollectUnknownTypeAttrs(*type, [](clang::attr::Kind kind) { |
| using enum clang::attr::Kind; |
| // annotate_type is usually meaningless and can be acked as |
| // understood. The major exception is lifetimes, which we do already |
| // handle separately. |
| return kind == AnnotateType; |
| }); |
| } |
| return mapped_type; |
| } |
| |
| absl::StatusOr<MappedType> Importer::ConvertUnattributedType( |
| const clang::Type* type, |
| const clang::tidy::lifetimes::ValueLifetimes* lifetimes, |
| std::optional<clang::RefQualifierKind> ref_qualifier_kind, bool nullable) { |
| // Qualifiers are handled separately in ConvertQualType(). |
| std::string type_string = clang::QualType(type, 0).getAsString(); |
| |
| assert(!lifetimes || IsSameCanonicalUnqualifiedType( |
| lifetimes->Type(), clang::QualType(type, 0))); |
| |
| if (auto override_type = GetTypeMapOverride(*type); |
| override_type.has_value()) { |
| return *std::move(override_type); |
| } else if (type->isPointerType() || type->isLValueReferenceType() || |
| type->isRValueReferenceType()) { |
| clang::QualType pointee_type = type->getPointeeType(); |
| std::optional<LifetimeId> lifetime; |
| const clang::tidy::lifetimes::ValueLifetimes* pointee_lifetimes = nullptr; |
| if (lifetimes) { |
| lifetime = |
| LifetimeId(lifetimes->GetPointeeLifetimes().GetLifetime().Id()); |
| pointee_lifetimes = &lifetimes->GetPointeeLifetimes().GetValueLifetimes(); |
| } |
| if (const auto* func_type = |
| pointee_type->getAs<clang::FunctionProtoType>()) { |
| // Assert that the function pointers/references always either 1) have no |
| // lifetime or 2) have `'static` lifetime (no other lifetime is allowed). |
| CHECK(!lifetime.has_value() || |
| (lifetime->value() == |
| clang::tidy::lifetimes::Lifetime::Static().Id())); |
| |
| clang::StringRef cc_call_conv = |
| clang::FunctionType::getNameForCallConv(func_type->getCallConv()); |
| CRUBIT_ASSIGN_OR_RETURN( |
| absl::string_view rs_abi, |
| ConvertCcCallConvIntoRsAbi(func_type->getCallConv())); |
| const clang::tidy::lifetimes::ValueLifetimes* return_lifetimes = nullptr; |
| if (pointee_lifetimes) { |
| return_lifetimes = |
| &pointee_lifetimes->GetFuncLifetimes().GetReturnLifetimes(); |
| } |
| CRUBIT_ASSIGN_OR_RETURN( |
| MappedType mapped_return_type, |
| ConvertQualType(func_type->getReturnType(), return_lifetimes, |
| ref_qualifier_kind)); |
| |
| std::vector<MappedType> mapped_param_types; |
| for (unsigned i = 0; i < func_type->getNumParams(); ++i) { |
| const clang::tidy::lifetimes::ValueLifetimes* param_lifetimes = nullptr; |
| if (pointee_lifetimes) { |
| param_lifetimes = |
| &pointee_lifetimes->GetFuncLifetimes().GetParamLifetimes(i); |
| } |
| CRUBIT_ASSIGN_OR_RETURN( |
| MappedType mapped_param_type, |
| ConvertQualType(func_type->getParamType(i), param_lifetimes, |
| ref_qualifier_kind)); |
| mapped_param_types.push_back(std::move(mapped_param_type)); |
| } |
| |
| if (type->isPointerType()) { |
| return MappedType::FuncPtr(cc_call_conv, rs_abi, lifetime, |
| std::move(mapped_return_type), |
| std::move(mapped_param_types)); |
| } else { |
| CHECK(type->isLValueReferenceType()); |
| return MappedType::FuncRef(cc_call_conv, rs_abi, lifetime, |
| std::move(mapped_return_type), |
| std::move(mapped_param_types)); |
| } |
| } |
| |
| CRUBIT_ASSIGN_OR_RETURN( |
| MappedType mapped_pointee_type, |
| ConvertQualType(pointee_type, pointee_lifetimes, ref_qualifier_kind)); |
| if (type->isPointerType()) { |
| return MappedType::PointerTo(std::move(mapped_pointee_type), lifetime, |
| ref_qualifier_kind, nullable); |
| } else if (type->isLValueReferenceType()) { |
| return MappedType::LValueReferenceTo(std::move(mapped_pointee_type), |
| lifetime); |
| } else { |
| CHECK(type->isRValueReferenceType()); |
| if (!lifetime.has_value()) { |
| return absl::UnimplementedError( |
| "Unsupported type: && without lifetime"); |
| } |
| return MappedType::RValueReferenceTo(std::move(mapped_pointee_type), |
| *lifetime); |
| } |
| } else if (const auto* builtin_type = |
| // Use getAsAdjusted instead of getAs so we don't desugar |
| // typedefs. |
| type->getAsAdjusted<clang::BuiltinType>()) { |
| switch (builtin_type->getKind()) { |
| case clang::BuiltinType::Bool: |
| return MappedType::Simple("bool", "bool"); |
| case clang::BuiltinType::Void: |
| return MappedType::Void(); |
| |
| // Floating-point numbers |
| // |
| // TODO(b/255768062): Generated bindings should explicitly check if |
| // `math.h` defines the `__STDC_IEC_559__` macro. |
| case clang::BuiltinType::Float: |
| return MappedType::Simple("f32", "float"); |
| case clang::BuiltinType::Double: |
| return MappedType::Simple("f64", "double"); |
| |
| // `char` |
| case clang::BuiltinType::Char_S: // 'char' in targets where it's signed |
| case clang::BuiltinType::Char_U: // 'char' in targets where it's unsigned |
| return MappedType::Simple("::core::ffi::c_char", "char"); |
| case clang::BuiltinType::SChar: // 'signed char', explicitly qualified |
| return MappedType::Simple("::core::ffi::c_schar", "signed char"); |
| case clang::BuiltinType::UChar: // 'unsigned char', explicitly qualified |
| return MappedType::Simple("::core::ffi::c_uchar", "unsigned char"); |
| |
| // Signed integers |
| case clang::BuiltinType::Short: |
| return MappedType::Simple("::core::ffi::c_short", "short"); |
| case clang::BuiltinType::Int: |
| return MappedType::Simple("::core::ffi::c_int", "int"); |
| case clang::BuiltinType::Long: |
| return MappedType::Simple("::core::ffi::c_long", "long"); |
| case clang::BuiltinType::LongLong: |
| return MappedType::Simple("::core::ffi::c_longlong", "long long"); |
| |
| // Unsigned integers |
| case clang::BuiltinType::UShort: |
| return MappedType::Simple("::core::ffi::c_ushort", "unsigned short"); |
| case clang::BuiltinType::UInt: |
| return MappedType::Simple("::core::ffi::c_uint", "unsigned int"); |
| case clang::BuiltinType::ULong: |
| return MappedType::Simple("::core::ffi::c_ulong", "unsigned long"); |
| case clang::BuiltinType::ULongLong: |
| return MappedType::Simple("::core::ffi::c_ulonglong", |
| "unsigned long long"); |
| |
| case clang::BuiltinType::Char16: |
| return MappedType::Simple("u16", "char16_t"); |
| case clang::BuiltinType::Char32: |
| return MappedType::Simple("u32", "char32_t"); |
| default: |
| return absl::UnimplementedError("Unsupported builtin type"); |
| } |
| } else if (const auto* tag_type = type->getAsAdjusted<clang::TagType>()) { |
| return ConvertTypeDecl(tag_type->getDecl()); |
| } else if (const auto* typedef_type = |
| type->getAsAdjusted<clang::TypedefType>()) { |
| return ConvertTypeDecl(typedef_type->getDecl()); |
| } else if (const auto* using_type = type->getAs<clang::UsingType>()) { |
| return ConvertTypeDecl(using_type->getFoundDecl()); |
| } else if (const auto* tst_type = |
| type->getAs<clang::TemplateSpecializationType>()) { |
| return ConvertTemplateSpecializationType(tst_type); |
| } else if (const auto* subst_type = |
| type->getAs<clang::SubstTemplateTypeParmType>()) { |
| return ConvertQualType(subst_type->getReplacementType(), lifetimes, |
| ref_qualifier_kind); |
| } else if (const auto* deduced_type = type->getAs<clang::DeducedType>()) { |
| // Deduction should have taken place earlier (e.g. via DeduceReturnType |
| // called from FunctionDeclImporter::Import). |
| CHECK(deduced_type->isDeduced()); |
| return ConvertQualType(deduced_type->getDeducedType(), lifetimes, |
| ref_qualifier_kind); |
| } |
| |
| return absl::UnimplementedError(absl::StrCat( |
| "Unsupported clang::Type class '", type->getTypeClassName(), "'")); |
| } |
| |
| // Returns a QualType with leading ElaboratedType nodes removed. |
| // |
| // This is analogous to getDesugaredType but *only* removes ElaboratedType |
| // sugar. |
| static clang::QualType GetUnelaboratedType(clang::QualType qual_type, |
| clang::ASTContext& ast_context) { |
| clang::QualifierCollector qualifiers; |
| while (true) { |
| const clang::Type* type = qualifiers.strip(qual_type); |
| if (const auto* elaborated = llvm::dyn_cast<clang::ElaboratedType>(type)) { |
| qual_type = elaborated->getNamedType(); |
| continue; |
| } |
| |
| return ast_context.getQualifiedType(type, qualifiers); |
| } |
| } |
| |
| absl::StatusOr<MappedType> Importer::ConvertQualType( |
| clang::QualType qual_type, |
| const clang::tidy::lifetimes::ValueLifetimes* lifetimes, |
| std::optional<clang::RefQualifierKind> ref_qualifier_kind, bool nullable) { |
| qual_type = GetUnelaboratedType(std::move(qual_type), ctx_); |
| std::string type_string = qual_type.getAsString(); |
| absl::StatusOr<MappedType> type = ConvertType( |
| qual_type.getTypePtr(), lifetimes, ref_qualifier_kind, nullable); |
| if (!type.ok()) { |
| absl::Status error = absl::UnimplementedError(absl::Substitute( |
| "Unsupported type '$0': $1", type_string, type.status().message())); |
| error.SetPayload(kTypeStatusPayloadUrl, absl::Cord(type_string)); |
| return error; |
| } |
| |
| // Handle cv-qualification. |
| type->cpp_type.is_const = qual_type.isConstQualified(); |
| if (qual_type.isVolatileQualified()) { |
| return absl::UnimplementedError( |
| absl::StrCat("Unsupported `volatile` qualifier: ", type_string)); |
| } |
| |
| return type; |
| } |
| |
| std::string Importer::GetMangledName(const clang::NamedDecl* named_decl) const { |
| if (auto record_decl = clang::dyn_cast<clang::RecordDecl>(named_decl)) { |
| // Mangled record names are used to 1) provide valid Rust identifiers for |
| // C++ template specializations, and 2) help build unique names for virtual |
| // upcast thunks. |
| llvm::SmallString<128> storage; |
| llvm::raw_svector_ostream buffer(storage); |
| mangler_->mangleCanonicalTypeName(ctx_.getRecordType(record_decl), buffer); |
| |
| // The Itanium mangler does not provide a way to get the mangled |
| // representation of a type. Instead, we call mangleTypeName() that |
| // returns the name of the RTTI typeinfo symbol, and remove the _ZTS |
| // prefix. |
| constexpr llvm::StringRef kZtsPrefix = "_ZTS"; |
| CHECK(buffer.str().take_front(4) == kZtsPrefix); |
| llvm::StringRef mangled_record_name = |
| buffer.str().drop_front(kZtsPrefix.size()); |
| |
| if (clang::isa<clang::ClassTemplateSpecializationDecl>(named_decl)) { |
| // We prepend __CcTemplateInst to reduce chances of conflict |
| // with regular C and C++ structs. |
| constexpr llvm::StringRef kCcTemplatePrefix = "__CcTemplateInst"; |
| return llvm::formatv("{0}{1}", kCcTemplatePrefix, mangled_record_name); |
| } |
| return std::string(mangled_record_name); |
| } |
| |
| if (!mangler_->shouldMangleDeclName(named_decl)) { |
| return named_decl->getIdentifier()->getName().str(); |
| } |
| |
| 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 = clang::dyn_cast<clang::CXXDestructorDecl>(named_decl)) { |
| decl = clang::GlobalDecl(dtor, clang::CXXDtorType::Dtor_Complete); |
| } else if (auto ctor = |
| clang::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::string Importer::GetNameForSourceOrder(const clang::Decl* decl) const { |
| // Implicit class template specializations and their methods all have the |
| // same source location. In order to provide deterministic order of the |
| // respective items in generated source code, we additionally use the |
| // mangled names when sorting the items. |
| if (auto* class_template_specialization_decl = |
| clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(decl)) { |
| return GetMangledName(class_template_specialization_decl); |
| } else if (auto* func_decl = clang::dyn_cast<clang::FunctionDecl>(decl)) { |
| return GetMangledName(func_decl); |
| } else if (auto* friend_decl = clang::dyn_cast<clang::FriendDecl>(decl)) { |
| if (auto* named_decl = friend_decl->getFriendDecl()) { |
| if (auto function_template_decl = |
| clang::dyn_cast<clang::FunctionTemplateDecl>(named_decl)) { |
| // Reach through the function template declaration for a function that |
| // can be mangled. |
| named_decl = function_template_decl->getTemplatedDecl(); |
| } |
| return GetMangledName(named_decl); |
| } else { |
| // This FriendDecl names a type. We don't import those, so we don't have |
| // to assign a name. |
| return ""; |
| } |
| } else { |
| return ""; |
| } |
| } |
| |
| absl::StatusOr<UnqualifiedIdentifier> Importer::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()) { |
| return absl::InvalidArgumentError("Missing identifier"); |
| } |
| |
| // `r#foo` syntax in Rust can't be used to escape `crate`, `self`, |
| // `super`, not `Self` identifiers - see |
| // https://doc.rust-lang.org/reference/identifiers.html#identifiers |
| if (name == "crate" || name == "self" || name == "super" || |
| name == "Self") { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Unescapable identifier: ", name)); |
| } |
| |
| return {Identifier(std::move(name))}; |
| } |
| case clang::DeclarationName::CXXConstructorName: |
| return {SpecialName::kConstructor}; |
| case clang::DeclarationName::CXXDestructorName: |
| return {SpecialName::kDestructor}; |
| case clang::DeclarationName::CXXOperatorName: |
| switch (named_decl->getDeclName().getCXXOverloadedOperator()) { |
| case clang::OO_None: |
| LOG(FATAL) << "No OO_None expected under CXXOperatorName branch"; |
| case clang::NUM_OVERLOADED_OPERATORS: |
| LOG(FATAL) << "No NUM_OVERLOADED_OPERATORS expected at runtime"; |
| // clang-format off |
| #define OVERLOADED_OPERATOR(name, spelling, ...) \ |
| case clang::OO_##name: { \ |
| return {Operator(spelling)}; \ |
| } |
| #include "clang/Basic/OperatorKinds.def" |
| #undef OVERLOADED_OPERATOR |
| // clang-format on |
| } |
| LOG(FATAL) << "The `switch` above should handle all cases"; |
| default: |
| // To be implemented later: CXXConversionFunctionName. |
| // 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 absl::UnimplementedError( |
| absl::StrCat("Unsupported name: ", named_decl->getNameAsString())); |
| } |
| } |
| |
| void Importer::MarkAsSuccessfullyImported(const clang::NamedDecl* decl) { |
| known_type_decls_.insert( |
| clang::cast<clang::TypeDecl>(CanonicalizeDecl(decl))); |
| } |
| |
| bool Importer::HasBeenAlreadySuccessfullyImported( |
| const clang::NamedDecl* decl) const { |
| return known_type_decls_.contains( |
| clang::cast<clang::TypeDecl>(CanonicalizeDecl(decl))); |
| } |
| |
| } // namespace crubit |