blob: f57db7883df3a1024715580dbec94bdbc104cbd6 [file] [log] [blame] [edit]
// 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
// This file defines an intermediate representation (IR) used between Clang AST
// and code generators that generate Rust bindings and C++ bindings
// implementation.
//
// All types in this file own their data. This IR is expected to outlive the
// Clang's AST context, therefore it cannot reference data owned by it.
#ifndef CRUBIT_RS_BINDINGS_FROM_CC_IR_H_
#define CRUBIT_RS_BINDINGS_FROM_CC_IR_H_
#include <stdint.h>
#include <cstddef>
#include <iomanip>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "common/strong_int.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/RawCommentList.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
namespace crubit {
namespace internal {
inline constexpr int kJsonIndent = 2;
} // namespace internal
// A name of a public header of the C++ library.
class HeaderName {
public:
explicit HeaderName(std::string name) : name_(std::move(name)) {}
absl::string_view IncludePath() const { return name_; }
llvm::json::Value ToJson() const;
template <typename H>
friend H AbslHashValue(H h, const HeaderName& header_name) {
return H::combine(std::move(h), header_name.name_);
}
private:
// Header pathname in the format suitable for a quote include.
std::string name_;
};
inline bool operator==(const HeaderName& lhs, const HeaderName& rhs) {
return lhs.IncludePath() == rhs.IncludePath();
}
inline std::ostream& operator<<(std::ostream& o, const HeaderName& h) {
return o << std::string(llvm::formatv("{0:2}", h.ToJson()));
}
// An int uniquely representing an Item. Since our IR goes through the JSON
// serialization/deserialization at the moment, we need a way to restore graph
// edges that don't follow the JSON tree structure (for example between types
// and records), as well as location of comments and items we don't yet support.
// We use ItemIds for this.
CRUBIT_DEFINE_STRONG_INT_TYPE(ItemId, uintptr_t);
inline std::string DebugStringFromDecl(const clang::Decl* decl) {
auto canonical_decl_id =
reinterpret_cast<uintptr_t>(decl->getCanonicalDecl());
auto decl_id = reinterpret_cast<uintptr_t>(decl);
std::string decl_name;
auto ostream = llvm::raw_string_ostream(decl_name);
decl->print(ostream);
ostream.flush();
return absl::StrFormat("Canonical DeclID: %d; DeclID: %d; decl: %s",
canonical_decl_id, decl_id, decl_name);
}
// A numerical ID that uniquely identifies a lifetime.
CRUBIT_DEFINE_STRONG_INT_TYPE(LifetimeId, int);
// A lifetime.
struct LifetimeName {
llvm::json::Value ToJson() const;
// Lifetime name. Unlike syn::Lifetime, this does not include the apostrophe.
//
// Note that this is not an identifier; the rules for what is a valid lifetime
// name are slightly different than for identifiers, so we simply use a
// std::string instead of an Identifier here.
std::string name;
LifetimeId id;
};
inline std::ostream& operator<<(std::ostream& o, const LifetimeName& l) {
return o << std::string(llvm::formatv("{0:2}", l.ToJson()));
}
// Whether a function is annotated with `CRUBIT_UNSAFE` or
// `CRUBIT_DISABLE_UNSAFE`. `[[clang::unsafe_buffer_usage]]` is also considered
// unsafe.
enum class SafetyAnnotation : char { kDisableUnsafe, kUnsafe, kUnannotated };
enum class PointerTypeKind { kRValueRef, kLValueRef, kNullable, kNonNull };
// Calling conventions for functions that are supported by Crubit.
//
// This is a subset of the calling conventions supported by Clang.
enum class CallingConv {
kC, // __attribute__((cdecl))
kX86VectorCall, // __attribute__((vectorcall))
kX86FastCall, // __attribute__((fastcall))
kX864ThisCall, // __attribute__((thiscall))
kX86StdCall, // __attribute__((stdcall))
kWin64, // __attribute__((ms_abi))
};
struct CcType {
llvm::json::Value ToJson() const;
struct FuncPointer {
// When true, this is a C++ function reference that maps to a Rust function
// pointer. When false, this is a C++ function pointer that maps to a Rust
// function pointer wrapped in an `Option`.
bool non_null;
CallingConv call_conv;
// param_and_return_types assumes the last type is the return type.
std::vector<CcType> param_and_return_types;
};
struct PointerType {
PointerTypeKind kind;
// `LifetimeId` is present if the C++ code contained an explicit
// lifetime annotation or if C++ lifetime annotation elision was enabled.
//
// Today, this is rare: the Clang lifetime annotation pass isn't stable or
// functional.
std::optional<LifetimeId> lifetime;
std::shared_ptr<CcType> pointee_type;
};
struct Primitive {
// One of: bool, void, float, double, char, signed char, unsigned char,
// short, int, long, long long, unsigned short, unsigned int, unsigned long,
// unsigned long long, char16_t, char32_t, ptrdiff_t, intptr_t, size_t,
// uintptr_t, std::ptrdiff_t, std::intptr_t, std::size_t, std::uintptr_t,
// int8_t, int16_t, int32_t, int64_t, std::int8_t, std::int16_t,
// std::int32_t, std::int64_t, uint8_t, uint16_t, uint32_t, uint64_t,
// std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t.
//
// If we wanted to be really pedantic, this could be an enum. However,
// this type is only read by Rust after serialization. So there's no reason
// to convert to an enum just to convert it back in a ToJson() method.
std::string spelling;
};
static CcType PointerTo(CcType pointee_type,
std::optional<LifetimeId> lifetime, bool nullable);
static CcType LValueReferenceTo(CcType pointee_type,
std::optional<LifetimeId> lifetime);
static CcType RValueReferenceTo(CcType pointee_type,
std::optional<LifetimeId> lifetime);
bool IsVoid() const {
const auto* primitive = std::get_if<CcType::Primitive>(&variant);
return primitive != nullptr && primitive->spelling == "void";
}
using Variant = std::variant<Primitive, PointerType, FuncPointer, ItemId>;
explicit CcType(Variant variant) : variant(std::move(variant)) {}
Variant variant;
bool is_const = false;
std::string unknown_attr = "";
};
inline std::ostream& operator<<(std::ostream& o, const CcType& type) {
return o << std::string(llvm::formatv("{0:2}", type.ToJson()));
}
// An identifier involved in bindings.
//
// For example, the identifier for the C++ function `int Add(int a, int b);`
// is `Identifier("Add")`.
//
// This also includes operator names, such as "operator==". Non-symbol tokens in
// the operator name are separated by a single space. For example:
//
// * `Identifier("operator==")`
// * `Identifier("operator new[]")`
// * `Identifier("operator co_await")`
//
// Invariants:
// `identifier` cannot be empty.
class Identifier {
public:
explicit Identifier(std::string identifier)
: identifier_(std::move(identifier)) {
CHECK(!identifier_.empty());
}
absl::string_view Ident() const { return identifier_; }
llvm::json::Value ToJson() const;
template <typename H>
friend H AbslHashValue(H h, const Identifier& i) {
return H::combine(std::move(h), i.identifier_);
}
bool operator==(const Identifier& other) const {
return identifier_ == other.identifier_;
}
private:
std::string identifier_;
};
inline std::ostream& operator<<(std::ostream& o, const Identifier& id) {
return o << std::setw(internal::kJsonIndent) << id.Ident();
}
// An integer value in the range [-2**63, 2**64). This is intended to be used
// to produce integer literals in Rust code while specifying the type
// out-of-band.
class IntegerConstant {
public:
explicit IntegerConstant(const llvm::APSInt& value) {
CHECK_LE(value.getSignificantBits(), 64);
is_negative_ = value < 0;
// TODO: double-check that the following is correct to adapt for
// https://github.com/llvm/llvm-project/commit/0a89825a289d149195be390003424adad026067f
// Before:
// wrapped_value_ = static_cast<uint64_t>(value.getExtValue());
wrapped_value_ = static_cast<uint64_t>(
value.isSigned() ? value.getSExtValue() : value.getZExtValue());
}
IntegerConstant(const IntegerConstant& other) = default;
IntegerConstant& operator=(const IntegerConstant& other) = default;
llvm::json::Value ToJson() const;
private:
// value < 0
bool is_negative_;
// value (mod 2**64)
uint64_t wrapped_value_;
};
class Operator {
public:
explicit Operator(std::string name) : name_(std::move(name)) {
CHECK(!name_.empty());
}
absl::string_view Name() const { return name_; }
llvm::json::Value ToJson() const;
private:
std::string name_;
};
inline std::ostream& operator<<(std::ostream& stream, const Operator& op) {
char first_char = op.Name()[0];
const char* separator = ('a' <= first_char) && (first_char <= 'z') ? " " : "";
return stream << std::setw(internal::kJsonIndent) << "`operator" << separator
<< op.Name() << "`";
}
// A function parameter.
//
// Examples:
// FuncParam of a C++ function `void Foo(int32_t a);` will be
// `FuncParam{.type=Type{"i32", "int32_t"}, .identifier=Identifier("foo"))`.
struct FuncParam {
llvm::json::Value ToJson() const;
CcType type;
Identifier identifier;
std::optional<std::string> unknown_attr;
};
inline std::ostream& operator<<(std::ostream& o, const FuncParam& param) {
return o << std::string(llvm::formatv("{0:2}", param.ToJson()));
}
enum SpecialName {
kDestructor,
kConstructor,
};
std::ostream& operator<<(std::ostream& o, const SpecialName& special_name);
// A generalized notion of identifier, or an "Unqualified Identifier" in C++
// jargon: https://en.cppreference.com/w/cpp/language/identifiers
//
// Note that constructors are given a separate variant, so that we can treat
// them differently. After all, they are not invoked or defined like normal
// functions.
using UnqualifiedIdentifier = std::variant<Identifier, Operator, SpecialName>;
llvm::json::Value toJSON(const UnqualifiedIdentifier& unqualified_identifier);
struct TranslatedUnqualifiedIdentifier {
UnqualifiedIdentifier cc_identifier;
std::optional<UnqualifiedIdentifier> crubit_rust_name;
UnqualifiedIdentifier& rs_identifier();
};
struct TranslatedIdentifier {
Identifier cc_identifier;
std::optional<Identifier> crubit_rust_name;
Identifier& rs_identifier();
};
struct MemberFuncMetadata {
enum ReferenceQualification : char {
kLValue, // void Foo() &;
kRValue, // void Foo() &&;
kUnqualified, // void Foo();
};
// TODO(lukasza): Consider extracting a separate ConstructorMetadata struct to
// account for the fact that `is_const` and `is_virtual` never applies to
// constructors.
struct InstanceMethodMetadata {
llvm::json::Value ToJson() const;
ReferenceQualification reference = kUnqualified;
bool is_const = false;
bool is_virtual = false;
};
llvm::json::Value ToJson() const;
// The type that this is a member function for.
ItemId record_id;
// Qualifiers for the instance method.
//
// If null, this is a static method.
std::optional<InstanceMethodMetadata> instance_method_metadata;
};
// A function involved in the bindings.
struct Func {
llvm::json::Value ToJson() const;
UnqualifiedIdentifier cc_name;
UnqualifiedIdentifier rs_name;
BazelLabel owning_target;
std::optional<std::string> doc_comment;
std::string mangled_name;
CcType return_type;
std::vector<FuncParam> params;
std::vector<LifetimeName> lifetime_params;
bool is_inline;
// If null, this is not a member function.
std::optional<MemberFuncMetadata> member_func_metadata;
bool is_extern_c = false;
bool is_noreturn = false;
bool is_variadic = false;
bool is_consteval = false;
std::optional<std::string> nodiscard;
std::optional<std::string> deprecated;
std::optional<std::string> unknown_attr;
bool has_c_calling_convention = true;
bool is_member_or_descendant_of_class_template = false;
SafetyAnnotation safety_annotation;
std::string source_loc;
ItemId id;
std::optional<ItemId> enclosing_item_id;
// The enclosing record that this function is a friend of.
//
// If present, this function should only generate top-level bindings if its
// arguments refer to this enclosing record according to the ADL rules.
// This is necessary because ADL is needed in order to find friend functions.
//
// This could in principle be resolved while generating the IR, but the richer
// Rust type modeling in src_code_gen makes it much easier to do on the
// consuming end.
std::optional<ItemId> adl_enclosing_record;
bool must_bind = false;
};
inline std::ostream& operator<<(std::ostream& o, const Func& f) {
return o << std::string(llvm::formatv("{0:2}", f.ToJson()));
}
// Access specifier for a member or base class.
enum AccessSpecifier {
kPublic,
kProtected,
kPrivate,
};
std::ostream& operator<<(std::ostream& o, const AccessSpecifier& access);
// A field (non-static member variable) of a record.
struct Field {
llvm::json::Value ToJson() const;
// Name of the field. This may be missing for "unnamed members" - see:
// - https://en.cppreference.com/w/c/language/struct
// - https://rust-lang.github.io/rfcs/2102-unnamed-fields.html
std::optional<Identifier> rust_identifier;
std::optional<Identifier> cpp_identifier;
std::optional<std::string> doc_comment;
absl::StatusOr<CcType> type;
AccessSpecifier access;
uint64_t offset; // Field offset in bits.
uint64_t size; // Field size in bits.
absl::StatusOr<std::optional<std::string>> unknown_attr;
bool is_no_unique_address; // True if the field is [[no_unique_address]].
bool is_bitfield; // True if the field is a bitfield.
bool is_inheritable; // True if the field is inheritable.
};
inline std::ostream& operator<<(std::ostream& o, const Field& f) {
return o << std::string(llvm::formatv("{0:2}", f.ToJson()));
}
// Information about special member functions.
//
// Nontrivial definitions are divided into two: there are nontrivial definitions
// which are nontrivial only due to a member variable which defines the special
// member function, and those which are nontrivial because the operation was
// user defined for the object itself, or for any base class.
//
// This allows us to sidestep calling C++ implementations of special member
// functions in narrow cases: even for a nontrivial special member function, if
// it is kNontrivialMembers, we can directly implement it in Rust in terms of
// the member variables.
enum class SpecialMemberFunc : char {
kTrivial,
// Nontrivial, but only because of a member variable with a nontrivial
// special member function.
kNontrivialMembers,
// Nontrivial because of a user-defined special member function in this or a
// base class. (May *also* be nontrivial due to member variables.)
kNontrivialUserDefined,
// Deleted or non-public.
kUnavailable,
};
llvm::json::Value toJSON(const SpecialMemberFunc& f);
inline std::ostream& operator<<(std::ostream& o, const SpecialMemberFunc& f) {
return o << std::string(llvm::formatv("{0:2}", toJSON(f)));
}
// A base class subobject of a struct or class.
struct BaseClass {
llvm::json::Value ToJson() const;
ItemId base_record_id;
// The offset the base class subobject is located at. This is always nonempty
// for nonvirtual inheritance, and always empty if a virtual base class is
// anywhere in the inheritance chain.
std::optional<int64_t> offset;
};
enum RecordType {
// `struct` in Rust and C++
kStruct,
// `union` in Rust and C++
kUnion,
// `class` in C++. This is distinct from `kStruct` to avoid generating
// `struct SomeClass` in `..._rs_api_impl.cc` and getting `-Wmismatched-tags`
// warnings (see also b/238212337).
kClass,
};
std::ostream& operator<<(std::ostream& o, const RecordType& record_type);
struct SizeAlign {
llvm::json::Value ToJson() const;
int64_t size;
int64_t alignment;
};
// Present on records that are bridge types.
struct BridgeType {
llvm::json::Value ToJson() const;
// From CRUBIT_BRIDGE_VOID_CONVERTERS.
struct BridgeVoidConverters {
std::string rust_name;
std::string rust_to_cpp_converter;
std::string cpp_to_rust_converter;
};
// From CRUBIT_BRIDGE.
struct Bridge {
std::string rust_name;
std::string abi_rust;
std::string abi_cpp;
};
struct StdOptional {
std::shared_ptr<CcType> inner_type;
};
struct StdPair {
std::shared_ptr<CcType> first_type;
std::shared_ptr<CcType> second_type;
};
struct ProtoMessageBridge {
std::string rust_name;
std::string abi_rust;
std::string abi_cpp;
};
struct StdString {};
std::variant<BridgeVoidConverters, Bridge, StdOptional, StdPair,
ProtoMessageBridge, StdString>
variant;
};
// TODO: Handle non-type template parameter.
// A template argument for a template specialization.
struct TemplateArg {
absl::StatusOr<CcType> type;
llvm::json::Value ToJson() const;
};
// A template specialization for a template record, containing information
// including the template name (like `ns::vector` for `ns::vector<int>`) and the
// template arguments (like [`int`, `float`] for `ns::map<int, float>`).
struct TemplateSpecialization {
llvm::json::Value ToJson() const;
bool is_string_view;
bool is_wstring_view;
BazelLabel defining_target;
std::string template_name;
std::vector<TemplateArg> template_args;
};
enum class TraitImplPolarity : int8_t { kNegative, kNone, kPositive };
// The set of traits to derive on the Rust type.
struct TraitDerives {
llvm::json::Value ToJson() const;
TraitImplPolarity* absl_nullable Polarity(absl::string_view trait);
// <internal link> start
TraitImplPolarity clone = TraitImplPolarity::kNone;
TraitImplPolarity copy = TraitImplPolarity::kNone;
TraitImplPolarity debug = TraitImplPolarity::kNone;
// <internal link> end
bool send = false;
bool sync = false;
std::vector<std::string> custom;
};
// A record (struct, class, union).
struct Record {
llvm::json::Value ToJson() const;
// `rs_name` and `cc_name` are typically equal, but they may be different for
// template instantiations (when `cc_name` is similar to `MyStruct<int>` and
// `rs_name` is similar to "__CcTemplateInst8MyStructIiE").
Identifier rs_name;
Identifier cc_name;
// 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.
std::string mangled_cc_name;
ItemId id;
// The target that owns this record. If this is a template instantiation, this
// is the target that instantiated this type (not the target that defined the
// template).
BazelLabel owning_target;
std::optional<TemplateSpecialization> template_specialization;
std::optional<std::string> unknown_attr;
std::optional<std::string> doc_comment;
std::optional<BridgeType> bridge_type;
std::string source_loc;
std::vector<BaseClass> unambiguous_public_bases;
std::vector<Field> fields;
std::vector<LifetimeName> lifetime_params;
SizeAlign size_align;
TraitDerives trait_derives;
// True if any base classes exist.
bool is_derived_class;
// True if the alignment may differ from what the fields would imply.
//
// For example, a base class or [[no_unique_address]] of alignment 8 should
// cause the record to have alignment at least 8. Since the field cannot be
// aligned due to layout issues, the parent struct must instead receive an
// alignment adjustment as necessary, via .override_alignment=true.
//
// More information: docs/struct_layout
bool override_alignment = false;
// True if the C++ class is annotated with `CRUBIT_UNSAFE`, otherwise false.
//
// Crubit considers some types unsafe, like pointers. If a function
// accepts a value of this type, it's assumed that basically anything it could
// possibly do with that value involves a risk of UB if that value is invalid
// (e.g. dangling). Any pointer-like type which has no lifetime / could be
// dangling/invalid should marked with is_unsafe_type. Some examples:
//
// * string_view
// * span
// * absl::FunctionRef
bool is_unsafe_type = false;
// Special member functions.
SpecialMemberFunc copy_constructor = SpecialMemberFunc::kUnavailable;
SpecialMemberFunc move_constructor = SpecialMemberFunc::kUnavailable;
SpecialMemberFunc destructor = SpecialMemberFunc::kUnavailable;
// Whether this type is passed by value as if it were a trivial type (the same
// as it would be if it were a struct in C).
//
// This can be either due to language rules (it *is* a trivial type), or due
// to the usage of a Clang attribute that forces trivial for calls:
//
// * https://eel.is/c++draft/class.temporary#3
// * https://clang.llvm.org/docs/AttributeReference.html#trivial-abi
bool is_trivial_abi = false;
// Whether this type can be inherited from.
//
// A type might not be inheritable if:
// * The type was explicitly marked final
// * A core function like the destructor was marked final
// * The type is a C++ union, which does not support inheritance
bool is_inheritable = false;
// Whether this type is abstract.
bool is_abstract = false;
// Whether this type is annotated with [[clang::warn_unused_result]].
// https://clang.llvm.org/docs/AttributeReference.html#nodiscard-warn-unused-result
std::optional<std::string> nodiscard = std::nullopt;
// Whether this `Record` corresponds to a C++ `union`, `struct`, or `class`.
RecordType record_type;
// Whether this type can be initialized using aggregate initialization syntax.
//
// For more context, see:
// * https://en.cppreference.com/w/cpp/types/is_aggregate
// * https://en.cppreference.com/w/cpp/language/aggregate_initialization
bool is_aggregate = false;
// It is an anonymous record with a typedef name.
bool is_anon_record_with_typedef = false;
// True when this record is created from an explicit class template
// instantiation definition (which is also what cc_template!{} macro results
// in).
bool is_explicit_class_template_instantiation_definition = false;
std::vector<ItemId> child_item_ids;
std::optional<ItemId> enclosing_item_id;
bool must_bind = false;
};
// A forward-declared record (e.g. `struct Foo;`)
struct IncompleteRecord {
llvm::json::Value ToJson() const;
Identifier cc_name;
Identifier rs_name;
ItemId id;
BazelLabel owning_target;
std::optional<std::string> unknown_attr;
RecordType record_type;
std::optional<ItemId> enclosing_item_id;
bool must_bind = false;
};
struct Enumerator {
llvm::json::Value ToJson() const;
Identifier identifier;
IntegerConstant value;
std::optional<std::string> unknown_attr;
};
struct Enum {
llvm::json::Value ToJson() const;
Identifier cc_name;
Identifier rs_name;
ItemId id;
BazelLabel owning_target;
std::string source_loc;
CcType underlying_type;
std::optional<std::vector<Enumerator>> enumerators;
std::optional<std::string> unknown_attr;
std::optional<ItemId> enclosing_item_id;
bool must_bind = false;
};
struct GlobalVar {
llvm::json::Value ToJson() const;
Identifier cc_name;
Identifier rs_name;
ItemId id;
BazelLabel owning_target;
std::string source_loc;
std::optional<std::string> mangled_name;
CcType type;
std::optional<std::string> unknown_attr;
std::optional<ItemId> enclosing_item_id;
bool must_bind = false;
};
inline std::ostream& operator<<(std::ostream& o, const Record& r) {
return o << std::string(llvm::formatv("{0:2}", r.ToJson()));
}
// A type alias (defined either using `typedef` or `using`).
struct TypeAlias {
llvm::json::Value ToJson() const;
Identifier cc_name;
Identifier rs_name;
ItemId id;
BazelLabel owning_target;
std::optional<std::string> doc_comment;
std::optional<std::string> unknown_attr;
CcType underlying_type;
std::string source_loc;
std::optional<ItemId> enclosing_item_id;
bool must_bind = false;
};
inline std::ostream& operator<<(std::ostream& o, const TypeAlias& t) {
return o << std::string(llvm::formatv("{0:2}", t.ToJson()));
}
// An error that stores its format string as well as the formatted message.
class FormattedError final {
public:
auto operator<=>(const FormattedError&) const = default;
template <typename H>
friend H AbslHashValue(H h, const FormattedError& e) {
return H::combine(std::move(h), e.fmt_, e.message_);
}
// Returns a FormattedError for a static string. The string is used as both
// the format string and the formatted message. Intended to be used only with
// string literals.
template <size_t N>
static FormattedError Static(const char (&array)[N]) {
return FormattedError(array, array);
}
// Returns a FormattedError built with `absl::StrCat()`. The first argument is
// taken as the format string. All arguments are concatenated to form the
// formatted message, with an extra `": "` inserted after the first argument.
template <size_t N, typename... Ts>
static FormattedError PrefixedStrCat(const char (&prefix)[N],
Ts&&... moreArgs) {
return FormattedError(
prefix, absl::StrCat(prefix, ": ", std::forward<Ts>(moreArgs)...));
}
// Returns a FormattedError built with `absl::Substitute()`.
template <size_t N, typename... Ts>
static FormattedError Substitute(const char (&format)[N], Ts&&... args) {
return FormattedError(format,
absl::Substitute(format, std::forward<Ts>(args)...));
}
// Extracts a format string from a status payload, if present.
static FormattedError FromStatus(absl::Status status);
absl::string_view fmt() const { return fmt_; }
absl::string_view message() const { return message_; }
llvm::json::Value ToJson() const;
// Type URL for use as an `absl::Status` payload.
static constexpr absl::string_view kFmtPayloadTypeUrl =
"type.googleapis.com/crubit.FormattedError.fmt";
private:
FormattedError(std::string fmt, std::string message)
: fmt_(fmt), message_(message) {}
// The format string that produced the error message, if available. This is
// used as an aggregation key for error reports.
std::string fmt_;
// Explanation of why we couldn't generate bindings.
std::string message_;
};
// A placeholder for an item that we can't generate bindings for (yet)
struct UnsupportedItem {
// Kind is used to indicate which item would cannot be wrapped.
enum class Kind {
kFunc,
kGlobalVar,
kStruct,
kUnion,
kClass,
kEnum,
kTypeAlias,
kNamespace,
kConstructor,
// Unnameable items include things like comments that do not result in
// Rust types.
kOther,
};
struct Path {
UnqualifiedIdentifier ident;
std::optional<ItemId> enclosing_item_id;
llvm::json::Value ToJson() const;
};
llvm::json::Value ToJson() const;
// TODO(forster): We could show the original declaration in the generated
// message (potentially also for successfully imported items).
// Qualified name of the item for which we couldn't generate bindings
std::string name;
// For unsupported items, we may generate markers in the Rust bindings to
// indicate that the item is not supported. This function returns the kind of
// unsupported item in order to generate such markers in the proper namespace
// (type, function, module).
Kind kind;
std::optional<Path> path;
std::vector<FormattedError> errors;
std::string source_loc;
ItemId id;
// Whether the item required binding (was annotated with `CRUBIT_MUST_BIND`).
// If this is true, binding generation will fail with a hard error.
bool must_bind;
};
inline std::ostream& operator<<(std::ostream& o, const UnsupportedItem& r) {
return o << std::string(llvm::formatv("{0:2}", r.ToJson()));
}
struct Comment {
llvm::json::Value ToJson() const;
std::string text;
ItemId id;
bool must_bind = false;
};
inline std::ostream& operator<<(std::ostream& o, const Comment& r) {
return o << std::string(llvm::formatv("{0:2}", r.ToJson()));
}
struct Namespace {
llvm::json::Value ToJson() const;
Identifier cc_name;
Identifier rs_name;
ItemId id;
ItemId canonical_namespace_id;
std::optional<std::string> unknown_attr;
BazelLabel owning_target;
std::vector<ItemId> child_item_ids;
std::optional<ItemId> enclosing_item_id;
bool is_inline = false;
bool must_bind = false;
};
inline std::ostream& operator<<(std::ostream& o, const Namespace& n) {
return o << std::string(llvm::formatv("{0:2}", n.ToJson()));
}
// Declare a module and use its contents.
//
// This is used to support extra Rust source files.
struct UseMod {
llvm::json::Value ToJson() const;
std::string path;
Identifier mod_name;
ItemId id;
bool must_bind = false;
};
inline std::ostream& operator<<(std::ostream& o, const UseMod& use_mod) {
return o << std::string(llvm::formatv("{0:2}", use_mod.ToJson()));
}
// A type which has no bindings generated, and instead uses an already-existing
// rust type.
struct ExistingRustType {
llvm::json::Value ToJson() const;
std::string rs_name;
std::string cc_name;
// The generic/template type parameters to the C++/Rust type.
std::vector<CcType> type_parameters;
BazelLabel owning_target;
// Size and alignment, if known.
// (These will not be known for a forward declaration, for example.)
std::optional<SizeAlign> size_align;
bool is_same_abi;
ItemId id;
bool must_bind = false;
};
inline std::ostream& operator<<(std::ostream& o,
const ExistingRustType& existing_rust_type) {
return o << std::string(llvm::formatv("{0:2}", existing_rust_type.ToJson()));
}
// A complete intermediate representation of bindings for publicly accessible
// declarations of a single C++ library.
struct IR {
llvm::json::Value ToJson() const;
template <typename T>
std::vector<const T*> get_items_if() const {
std::vector<const T*> filtered_items;
for (const auto& item : items) {
if (auto* filtered_item = std::get_if<T>(&item)) {
filtered_items.push_back(filtered_item);
}
}
return filtered_items;
}
template <typename T>
std::vector<T*> get_items_if() {
std::vector<T*> filtered_items;
for (auto& item : items) {
if (auto* filtered_item = std::get_if<T>(&item)) {
filtered_items.push_back(const_cast<T*>(filtered_item));
}
}
return filtered_items;
}
// Collection of public headers that were used to construct the AST this `IR`.
//
// In production, these come from the `--public_headers` cmdline flag.
// Note that the order of the headers might be significant and needs to be
// preserved.
std::vector<HeaderName> public_headers;
BazelLabel current_target;
using Item = std::variant<Func, Record, IncompleteRecord, Enum, TypeAlias,
GlobalVar, UnsupportedItem, Comment, Namespace,
UseMod, ExistingRustType>;
std::vector<Item> items;
absl::flat_hash_map<BazelLabel, std::vector<ItemId>> top_level_item_ids;
// Empty string signals that the bindings should be generated in the crate
// root. This is the default state.
//
// Non-empty value represents the name of the first-level submodule inside of
// which bindings should be generated. This is how we generate bindings for
// class template instantiations - we put all generated bindings into a hidden
// module of the user crate so everything can be compiled in one rustc
// invocation (this enables us to access types introduced in the user crate
// for template instantiations in the future).
//
// TODO(hlopko): Replace empty strings with std::optional<std::string>
// throughout the codebase
std::string crate_root_path;
absl::flat_hash_map<BazelLabel, absl::flat_hash_set<std::string>>
crubit_features;
};
void SetMustBindItem(IR::Item& item);
inline std::string IrToJson(const IR& ir) {
return std::string(llvm::formatv("{0:2}", ir.ToJson()));
}
inline std::ostream& operator<<(std::ostream& o, const IR& ir) {
return o << IrToJson(ir);
}
// Utility function to convert items to string.
std::string ItemToString(const IR::Item& item);
inline std::string ItemToString(const std::optional<IR::Item>& item) {
if (item.has_value()) return ItemToString(*item);
return "null";
}
} // namespace crubit
#endif // CRUBIT_RS_BINDINGS_FROM_CC_IR_H_