| // 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/ir.h" |
| |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/log/check.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/cord.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| #include "common/string_type.h" |
| #include "common/strong_int.h" |
| #include "clang/AST/Type.h" |
| #include "llvm/Support/JSON.h" |
| |
| namespace crubit { |
| |
| template <class T> |
| llvm::json::Value toJSON(const T& t) { |
| return t.ToJson(); |
| } |
| |
| template <typename TTag, typename TInt> |
| llvm::json::Value toJSON(const crubit::StrongInt<TTag, TInt> strong_int) { |
| return llvm::json::Value(strong_int.value()); |
| } |
| |
| template <typename TTag> |
| llvm::json::Value toJSON(const crubit::StringType<TTag> string_type) { |
| return llvm::json::Value(string_type.value()); |
| } |
| |
| template <class T> |
| llvm::json::Value toJSON(const absl::StatusOr<T>& t) { |
| if (t.ok()) { |
| return llvm::json::Object{{"Ok", *t}}; |
| } |
| return llvm::json::Object{{"Err", std::string(t.status().message())}}; |
| } |
| |
| llvm::json::Value HeaderName::ToJson() const { |
| return llvm::json::Object{ |
| {"name", name_}, |
| }; |
| } |
| |
| llvm::json::Value LifetimeName::ToJson() const { |
| return llvm::json::Object{ |
| {"name", name}, |
| {"id", id}, |
| }; |
| } |
| |
| llvm::json::Value RsType::ToJson() const { |
| return llvm::json::Object{ |
| {"name", decl_id.has_value() ? llvm::json::Value(nullptr) |
| : llvm::json::Value(name)}, |
| {"lifetime_args", lifetime_args}, |
| {"type_args", type_args}, |
| {"unknown_attr", unknown_attr}, |
| {"decl_id", decl_id}, |
| }; |
| } |
| |
| llvm::json::Value CcType::ToJson() const { |
| return llvm::json::Object{ |
| {"name", decl_id.has_value() ? llvm::json::Value(nullptr) |
| : llvm::json::Value(name)}, |
| {"is_const", is_const}, |
| {"type_args", type_args}, |
| {"decl_id", decl_id}, |
| }; |
| } |
| |
| namespace { |
| enum class ValueCategory { kLvalue, kRvalue }; |
| |
| MappedType PointerOrReferenceTo( |
| MappedType pointee_type, absl::string_view cc_ptr_name, |
| ValueCategory value_category, std::optional<LifetimeId> lifetime, |
| std::optional<clang::RefQualifierKind> ref_qualifier_kind, bool nullable) { |
| bool has_lifetime = lifetime.has_value(); |
| absl::string_view rs_name; |
| if (value_category == ValueCategory::kLvalue) { |
| if (has_lifetime) { |
| if (ref_qualifier_kind.has_value() && |
| ref_qualifier_kind.value() == clang::RefQualifierKind::RQ_RValue) { |
| rs_name = pointee_type.cpp_type.is_const ? internal::kRustRvalueRefConst |
| : internal::kRustRvalueRefMut; |
| } else { |
| rs_name = pointee_type.cpp_type.is_const ? internal::kRustRefConst |
| : internal::kRustRefMut; |
| } |
| } else { |
| rs_name = pointee_type.cpp_type.is_const ? internal::kRustPtrConst |
| : internal::kRustPtrMut; |
| } |
| } else { |
| CHECK(has_lifetime); |
| rs_name = pointee_type.cpp_type.is_const ? internal::kRustRvalueRefConst |
| : internal::kRustRvalueRefMut; |
| } |
| auto pointer_type = |
| MappedType::Simple(std::string(rs_name), std::string(cc_ptr_name)); |
| if (has_lifetime) { |
| pointer_type.rs_type.lifetime_args.push_back(*std::move(lifetime)); |
| } |
| pointer_type.rs_type.type_args.push_back(std::move(pointee_type.rs_type)); |
| if (has_lifetime && nullable) { |
| pointer_type.rs_type = |
| RsType{.name = "Option", .type_args = {pointer_type.rs_type}}; |
| } |
| pointer_type.cpp_type.type_args.push_back(std::move(pointee_type.cpp_type)); |
| return pointer_type; |
| } |
| } // namespace |
| |
| MappedType MappedType::PointerTo( |
| MappedType pointee_type, std::optional<LifetimeId> lifetime, |
| std::optional<clang::RefQualifierKind> ref_qualifier_kind, bool nullable) { |
| return PointerOrReferenceTo(std::move(pointee_type), internal::kCcPtr, |
| ValueCategory::kLvalue, lifetime, |
| ref_qualifier_kind, nullable); |
| } |
| |
| MappedType MappedType::LValueReferenceTo(MappedType pointee_type, |
| std::optional<LifetimeId> lifetime) { |
| return PointerOrReferenceTo(std::move(pointee_type), internal::kCcLValueRef, |
| ValueCategory::kLvalue, lifetime, |
| /*ref_qualifier_kind=*/std::nullopt, |
| /*nullable=*/false); |
| } |
| |
| MappedType MappedType::RValueReferenceTo(MappedType pointee_type, |
| LifetimeId lifetime) { |
| return PointerOrReferenceTo(std::move(pointee_type), internal::kCcRValueRef, |
| ValueCategory::kRvalue, lifetime, |
| /*ref_qualifier_kind=*/std::nullopt, |
| /*nullable=*/false); |
| } |
| |
| MappedType MappedType::FuncPtr(absl::string_view cc_call_conv, |
| absl::string_view rs_abi, |
| std::optional<LifetimeId> lifetime, |
| MappedType return_type, |
| std::vector<MappedType> param_types) { |
| MappedType result = FuncRef(cc_call_conv, rs_abi, lifetime, |
| std::move(return_type), std::move(param_types)); |
| |
| CHECK_EQ(result.cpp_type.name, internal::kCcLValueRef); |
| result.cpp_type.name = std::string(internal::kCcPtr); |
| |
| RsType rs_func_ptr_type = std::move(result.rs_type); |
| CHECK(rs_func_ptr_type.name.substr(0, internal::kRustFuncPtr.length()) == |
| internal::kRustFuncPtr); |
| result.rs_type = |
| RsType{.name = "Option", .type_args = {std::move(rs_func_ptr_type)}}; |
| |
| return result; |
| } |
| |
| MappedType MappedType::FuncRef(absl::string_view cc_call_conv, |
| absl::string_view rs_abi, |
| std::optional<LifetimeId> lifetime, |
| MappedType return_type, |
| std::vector<MappedType> param_types) { |
| std::vector<MappedType> type_args = std::move(param_types); |
| type_args.push_back(std::move(return_type)); |
| |
| std::vector<CcType> cpp_type_args; |
| std::vector<RsType> rs_type_args; |
| cpp_type_args.reserve(type_args.size()); |
| rs_type_args.reserve(type_args.size()); |
| for (MappedType& type_arg : type_args) { |
| cpp_type_args.push_back(std::move(type_arg.cpp_type)); |
| rs_type_args.push_back(std::move(type_arg.rs_type)); |
| } |
| |
| CcType cc_func_value_type = CcType{ |
| .name = absl::StrCat(internal::kCcFuncValue, " ", cc_call_conv), |
| .type_args = std::move(cpp_type_args), |
| }; |
| CcType cc_func_ref_type = CcType{.name = std::string(internal::kCcLValueRef), |
| .type_args = {cc_func_value_type}}; |
| |
| // Rust cannot express a function *value* type, only function pointer types. |
| RsType rs_func_ptr_type = RsType{ |
| .name = absl::StrCat(internal::kRustFuncPtr, " ", rs_abi), |
| .type_args = std::move(rs_type_args), |
| }; |
| if (lifetime.has_value()) |
| rs_func_ptr_type.lifetime_args.push_back(*std::move(lifetime)); |
| |
| return MappedType{ |
| .rs_type = std::move(rs_func_ptr_type), |
| .cpp_type = std::move(cc_func_ref_type), |
| }; |
| } |
| |
| llvm::json::Value MappedType::ToJson() const { |
| return llvm::json::Object{ |
| {"rs_type", rs_type}, |
| {"cpp_type", cpp_type}, |
| }; |
| } |
| |
| llvm::json::Value Identifier::ToJson() const { |
| return llvm::json::Object{ |
| {"identifier", identifier_}, |
| }; |
| } |
| |
| llvm::json::Value IntegerConstant::ToJson() const { |
| return llvm::json::Object{ |
| {"is_negative", is_negative_}, |
| {"wrapped_value", wrapped_value_}, |
| }; |
| } |
| |
| llvm::json::Value Operator::ToJson() const { |
| return llvm::json::Object{ |
| {"name", name_}, |
| }; |
| } |
| |
| static std::string SpecialNameToString(SpecialName special_name) { |
| switch (special_name) { |
| case SpecialName::kDestructor: |
| return "Destructor"; |
| case SpecialName::kConstructor: |
| return "Constructor"; |
| } |
| } |
| |
| llvm::json::Value toJSON(const UnqualifiedIdentifier& unqualified_identifier) { |
| if (auto* id = std::get_if<Identifier>(&unqualified_identifier)) { |
| return llvm::json::Object{ |
| {"Identifier", *id}, |
| }; |
| } else if (auto* op = std::get_if<Operator>(&unqualified_identifier)) { |
| return llvm::json::Object{ |
| {"Operator", *op}, |
| }; |
| } else { |
| SpecialName special_name = std::get<SpecialName>(unqualified_identifier); |
| return llvm::json::Object{ |
| {SpecialNameToString(special_name), nullptr}, |
| }; |
| } |
| } |
| |
| llvm::json::Value FuncParam::ToJson() const { |
| return llvm::json::Object{ |
| {"type", type}, |
| {"identifier", identifier}, |
| {"unknown_attr", unknown_attr}, |
| }; |
| } |
| |
| std::ostream& operator<<(std::ostream& o, const SpecialName& special_name) { |
| return o << SpecialNameToString(special_name); |
| } |
| |
| llvm::json::Value MemberFuncMetadata::InstanceMethodMetadata::ToJson() const { |
| const char* reference_str = nullptr; |
| switch (reference) { |
| case MemberFuncMetadata::kLValue: |
| reference_str = "LValue"; |
| break; |
| case MemberFuncMetadata::kRValue: |
| reference_str = "RValue"; |
| break; |
| case MemberFuncMetadata::kUnqualified: |
| reference_str = "Unqualified"; |
| break; |
| } |
| |
| return llvm::json::Object{ |
| {"reference", reference_str}, |
| {"is_const", is_const}, |
| {"is_virtual", is_virtual}, |
| }; |
| } |
| |
| llvm::json::Value MemberFuncMetadata::ToJson() const { |
| return llvm::json::Object{ |
| {"record_id", record_id}, |
| {"instance_method_metadata", instance_method_metadata}, |
| }; |
| } |
| |
| llvm::json::Value TypeMapOverride::ToJson() const { |
| llvm::json::Object override{ |
| {"rs_name", rs_name}, |
| {"cc_name", cc_name}, |
| {"type_parameters", type_parameters}, |
| {"owning_target", owning_target}, |
| {"is_same_abi", is_same_abi}, |
| {"id", id}, |
| }; |
| if (size_align.has_value()) { |
| override.insert({"size_align", size_align->ToJson()}); |
| } |
| |
| return llvm::json::Object{ |
| {"TypeMapOverride", std::move(override)}, |
| }; |
| } |
| |
| llvm::json::Value UseMod::ToJson() const { |
| llvm::json::Object use_mod{ |
| {"path", path}, |
| {"mod_name", mod_name}, |
| {"id", id}, |
| }; |
| |
| return llvm::json::Object{ |
| {"UseMod", std::move(use_mod)}, |
| }; |
| } |
| |
| llvm::json::Value Func::ToJson() const { |
| llvm::json::Object func{ |
| {"name", name}, |
| {"owning_target", owning_target}, |
| {"doc_comment", doc_comment}, |
| {"mangled_name", mangled_name}, |
| {"return_type", return_type}, |
| {"params", params}, |
| {"lifetime_params", lifetime_params}, |
| {"is_inline", is_inline}, |
| {"member_func_metadata", member_func_metadata}, |
| {"is_extern_c", is_extern_c}, |
| {"is_noreturn", is_noreturn}, |
| {"nodiscard", nodiscard}, |
| {"deprecated", deprecated}, |
| {"has_c_calling_convention", has_c_calling_convention}, |
| {"is_member_or_descendant_of_class_template", |
| is_member_or_descendant_of_class_template}, |
| {"source_loc", source_loc}, |
| {"id", id}, |
| {"enclosing_item_id", enclosing_item_id}, |
| {"adl_enclosing_record", adl_enclosing_record}, |
| }; |
| |
| return llvm::json::Object{ |
| {"Func", std::move(func)}, |
| }; |
| } |
| |
| static std::string AccessToString(AccessSpecifier access) { |
| switch (access) { |
| case kPublic: |
| return "Public"; |
| case kProtected: |
| return "Protected"; |
| case kPrivate: |
| return "Private"; |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& o, const AccessSpecifier& access) { |
| return o << AccessToString(access); |
| } |
| |
| llvm::json::Value Field::ToJson() const { |
| return llvm::json::Object{ |
| {"identifier", identifier}, |
| {"doc_comment", doc_comment}, |
| {"type", type}, |
| {"access", AccessToString(access)}, |
| {"offset", offset}, |
| {"size", size}, |
| {"unknown_attr", unknown_attr}, |
| {"is_no_unique_address", is_no_unique_address}, |
| {"is_bitfield", is_bitfield}, |
| {"is_inheritable", is_inheritable}, |
| }; |
| } |
| |
| llvm::json::Value toJSON(const SpecialMemberFunc& f) { |
| switch (f) { |
| case SpecialMemberFunc::kTrivial: |
| return "Trivial"; |
| case SpecialMemberFunc::kNontrivialMembers: |
| return "NontrivialMembers"; |
| case SpecialMemberFunc::kNontrivialUserDefined: |
| return "NontrivialUserDefined"; |
| case SpecialMemberFunc::kUnavailable: |
| return "Unavailable"; |
| } |
| } |
| |
| llvm::json::Value BaseClass::ToJson() const { |
| return llvm::json::Object{ |
| {"base_record_id", base_record_id}, |
| {"offset", offset}, |
| }; |
| } |
| |
| static std::string RecordTypeToString(RecordType record_type) { |
| switch (record_type) { |
| case kStruct: |
| return "Struct"; |
| case kUnion: |
| return "Union"; |
| case kClass: |
| return "Class"; |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& o, const RecordType& record_type) { |
| return o << RecordTypeToString(record_type); |
| } |
| |
| llvm::json::Value IncompleteRecord::ToJson() const { |
| llvm::json::Object record{ |
| {"cc_name", cc_name}, |
| {"rs_name", rs_name}, |
| {"id", id}, |
| {"owning_target", owning_target}, |
| {"unknown_attr", unknown_attr}, |
| {"record_type", RecordTypeToString(record_type)}, |
| {"enclosing_item_id", enclosing_item_id}, |
| }; |
| |
| return llvm::json::Object{ |
| {"IncompleteRecord", std::move(record)}, |
| }; |
| } |
| |
| llvm::json::Value SizeAlign::ToJson() const { |
| return llvm::json::Object{ |
| {"size", size}, |
| {"alignment", alignment}, |
| }; |
| } |
| |
| llvm::json::Value BridgeTypeInfo::ToJson() const { |
| return llvm::json::Object{ |
| {"bridge_type", bridge_type}, |
| {"rust_to_cpp_converter", rust_to_cpp_converter}, |
| {"cpp_to_rust_converter", cpp_to_rust_converter}, |
| }; |
| } |
| |
| llvm::json::Value TemplateArg::ToJson() const { |
| return llvm::json::Object{ |
| {"type", type}, |
| }; |
| } |
| |
| llvm::json::Value TemplateSpecialization::ToJson() const { |
| return llvm::json::Object{ |
| {"template_name", template_name}, |
| {"template_args", template_args}, |
| }; |
| } |
| |
| llvm::json::Value Record::ToJson() const { |
| std::vector<llvm::json::Value> json_item_ids; |
| json_item_ids.reserve(child_item_ids.size()); |
| for (const auto& id : child_item_ids) { |
| json_item_ids.push_back(id.value()); |
| } |
| |
| llvm::json::Object record{ |
| {"rs_name", rs_name}, |
| {"cc_name", cc_name}, |
| {"cc_preferred_name", cc_preferred_name}, |
| {"mangled_cc_name", mangled_cc_name}, |
| {"id", id}, |
| {"owning_target", owning_target}, |
| {"defining_target", defining_target}, |
| {"template_specialization", template_specialization}, |
| {"unknown_attr", unknown_attr}, |
| {"doc_comment", doc_comment}, |
| {"bridge_type_info", bridge_type_info}, |
| {"source_loc", source_loc}, |
| {"unambiguous_public_bases", unambiguous_public_bases}, |
| {"fields", fields}, |
| {"lifetime_params", lifetime_params}, |
| {"size_align", size_align.ToJson()}, |
| {"is_derived_class", is_derived_class}, |
| {"override_alignment", override_alignment}, |
| {"copy_constructor", copy_constructor}, |
| {"move_constructor", move_constructor}, |
| {"destructor", destructor}, |
| {"is_trivial_abi", is_trivial_abi}, |
| {"is_inheritable", is_inheritable}, |
| {"is_abstract", is_abstract}, |
| {"record_type", RecordTypeToString(record_type)}, |
| {"is_aggregate", is_aggregate}, |
| {"is_anon_record_with_typedef", is_anon_record_with_typedef}, |
| {"child_item_ids", std::move(json_item_ids)}, |
| {"enclosing_item_id", enclosing_item_id}, |
| }; |
| |
| return llvm::json::Object{ |
| {"Record", std::move(record)}, |
| }; |
| } |
| |
| llvm::json::Value Enumerator::ToJson() const { |
| return llvm::json::Object{ |
| {"identifier", identifier}, |
| {"value", value}, |
| {"unknown_attr", unknown_attr}, |
| }; |
| } |
| |
| llvm::json::Value Enum::ToJson() const { |
| llvm::json::Object enum_ir{ |
| {"identifier", identifier}, |
| {"id", id}, |
| {"owning_target", owning_target}, |
| {"source_loc", source_loc}, |
| {"underlying_type", underlying_type}, |
| {"enumerators", enumerators}, |
| {"unknown_attr", unknown_attr}, |
| {"enclosing_item_id", enclosing_item_id}, |
| }; |
| |
| return llvm::json::Object{ |
| {"Enum", std::move(enum_ir)}, |
| }; |
| } |
| |
| llvm::json::Value TypeAlias::ToJson() const { |
| llvm::json::Object type_alias{ |
| {"identifier", identifier}, |
| {"id", id}, |
| {"owning_target", owning_target}, |
| {"unknown_attr", unknown_attr}, |
| {"doc_comment", doc_comment}, |
| {"underlying_type", underlying_type}, |
| {"source_loc", source_loc}, |
| {"enclosing_item_id", enclosing_item_id}, |
| }; |
| |
| return llvm::json::Object{ |
| {"TypeAlias", std::move(type_alias)}, |
| }; |
| } |
| |
| FormattedError FormattedError::FromStatus(absl::Status status) { |
| std::optional<absl::Cord> fmt_cord = |
| status.GetPayload(FormattedError::kFmtPayloadTypeUrl); |
| std::string fmt; |
| if (fmt_cord) { |
| fmt = std::string(*fmt_cord); |
| } else { |
| fmt = absl::StrCat("(unannotated `", |
| absl::StatusCodeToString(status.code()), "` status)"); |
| } |
| return FormattedError(fmt, std::string(status.message())); |
| } |
| |
| llvm::json::Value FormattedError::ToJson() const { |
| return llvm::json::Object{ |
| {"fmt", fmt_}, |
| {"message", message_}, |
| }; |
| } |
| |
| llvm::json::Value UnsupportedItem::ToJson() const { |
| std::vector<llvm::json::Value> json_errors; |
| json_errors.reserve(errors.size()); |
| for (const auto& error : errors) { |
| json_errors.push_back(error.ToJson()); |
| } |
| |
| llvm::json::Object unsupported{ |
| {"name", name}, |
| {"errors", json_errors}, |
| {"source_loc", source_loc}, |
| {"id", id}, |
| }; |
| |
| return llvm::json::Object{ |
| {"UnsupportedItem", std::move(unsupported)}, |
| }; |
| } |
| |
| llvm::json::Value Comment::ToJson() const { |
| llvm::json::Object comment{ |
| {"text", text}, |
| {"id", id}, |
| }; |
| comment["id"] = id.value(); |
| return llvm::json::Object{ |
| {"Comment", std::move(comment)}, |
| }; |
| } |
| |
| llvm::json::Value Namespace::ToJson() const { |
| std::vector<llvm::json::Value> json_item_ids; |
| json_item_ids.reserve(child_item_ids.size()); |
| for (const auto& id : child_item_ids) { |
| json_item_ids.push_back(id.value()); |
| } |
| |
| llvm::json::Object ns{ |
| {"name", name}, |
| {"id", id}, |
| {"canonical_namespace_id", canonical_namespace_id}, |
| {"unknown_attr", unknown_attr}, |
| {"owning_target", owning_target}, |
| {"child_item_ids", std::move(json_item_ids)}, |
| {"enclosing_item_id", enclosing_item_id}, |
| {"is_inline", is_inline}, |
| }; |
| |
| return llvm::json::Object{ |
| {"Namespace", std::move(ns)}, |
| }; |
| } |
| |
| llvm::json::Value IR::ToJson() const { |
| std::vector<llvm::json::Value> json_items; |
| json_items.reserve(items.size()); |
| for (const auto& item : items) { |
| std::visit([&](auto&& item) { json_items.push_back(item.ToJson()); }, item); |
| } |
| CHECK_EQ(json_items.size(), items.size()); |
| |
| std::vector<llvm::json::Value> top_level_ids; |
| top_level_ids.reserve(top_level_item_ids.size()); |
| for (const auto& id : top_level_item_ids) { |
| top_level_ids.push_back(id.value()); |
| } |
| |
| llvm::json::Object features_json; |
| for (const auto& [target, features] : crubit_features) { |
| std::vector<llvm::json::Value> feature_array; |
| for (const std::string& feature : features) { |
| feature_array.push_back(feature); |
| } |
| features_json[target.value()] = std::move(feature_array); |
| } |
| |
| llvm::json::Object result{ |
| {"public_headers", public_headers}, |
| {"current_target", current_target}, |
| {"items", std::move(json_items)}, |
| {"top_level_item_ids", std::move(top_level_ids)}, |
| {"crubit_features", std::move(features_json)}, |
| }; |
| if (!crate_root_path.empty()) { |
| result["crate_root_path"] = crate_root_path; |
| } |
| return std::move(result); |
| } |
| |
| std::string ItemToString(const IR::Item& item) { |
| return std::visit( |
| [&](auto&& item) { return llvm::formatv("{0}", item.ToJson()); }, item); |
| } |
| |
| } // namespace crubit |