Make items that stem from decl context have a list of children item ids and generate code recursively
After this cl:
* We use a new method `Importer::GetItemIdsInSourceOrder()` to obtain a list of their children items (including comments and unsupported items, excluding children `decl`s whose `ImportDecl()` returned `std::nullopt`, as they don't have a corresponding item.)
* We generate source for items recursively, starting from `flat_ir.top_level_item_ids`.
* This required some changes to `generate_record()`, as it used to not be responsible for generating (unsupported) items for its own children, and now it is.
* Comments: We store all the comments, so we can find the relevant comments for each decl context.
* We now generate free comments that appear within a `Record`; however, comments between fields now appear out of place, after the struct definition. I will address this in a followup cl.
PiperOrigin-RevId: 440906345
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index c06fcbf..2a50fba 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -48,6 +48,7 @@
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/Specifiers.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Sema/Sema.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/Optional.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/STLExtras.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/ErrorHandling.h"
@@ -188,54 +189,6 @@
} // namespace
-std::vector<clang::RawComment*> Importer::ImportFreeComments() {
- clang::SourceManager& sm = ctx_.getSourceManager();
-
- // We put all comments into an ordered set in source order. Later we'll remove
- // the comments that we don't want or that we get by other means.
- auto source_order = [&sm](const clang::SourceLocation& a,
- const clang::SourceLocation& b) {
- return b.isValid() && (a.isInvalid() || sm.isBeforeInTranslationUnit(a, b));
- };
- auto ordered_comments = std::map<clang::SourceLocation, clang::RawComment*,
- decltype(source_order)>(source_order);
-
- // We start off by getting the comments from all entry header files...
- for (const auto& header : invocation_.entry_headers_) {
- if (auto file = sm.getFileManager().getFile(header.IncludePath())) {
- if (auto comments = ctx_.Comments.getCommentsInFile(
- sm.getOrCreateFileID(*file, clang::SrcMgr::C_User))) {
- for (const auto& [_, comment] : *comments) {
- ordered_comments.insert({comment->getBeginLoc(), comment});
- }
- }
- }
- }
-
- // ... and then we remove those that "conflict" with an IR item.
- for (const auto& [decl, result] : import_cache_) {
- if (result.has_value()) {
- // Remove doc comments of imported items.
- if (auto raw_comment = ctx_.getRawCommentForDeclNoCache(decl)) {
- ordered_comments.erase(raw_comment->getBeginLoc());
- }
- // Remove comments that are within a visited decl.
- // TODO(forster): We should retain floating comments in decls like
- // records and namespaces.
- ordered_comments.erase(ordered_comments.lower_bound(decl->getBeginLoc()),
- ordered_comments.upper_bound(decl->getEndLoc()));
- }
- }
-
- // Return the remaining comments as a `std::vector`.
- std::vector<clang::RawComment*> result;
- result.reserve(ordered_comments.size());
- for (auto& [_, comment] : ordered_comments) {
- result.push_back(comment);
- }
- return result;
-}
-
// 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
@@ -267,12 +220,30 @@
return 999;
}
-void Importer::Import(clang::TranslationUnitDecl* translation_unit_decl) {
- ImportDeclsFromDeclContext(translation_unit_decl);
+class SourceLocationComparator {
+ public:
+ const bool operator()(const clang::SourceLocation& a,
+ const clang::SourceLocation& b) const {
+ return b.isValid() && a.isValid() && sm.isBeforeInTranslationUnit(a, b);
+ }
+ const bool operator()(const clang::RawComment* a,
+ const clang::SourceLocation& b) const {
+ return this->operator()(a->getBeginLoc(), b);
+ }
+ const bool operator()(const clang::SourceLocation& a,
+ const clang::RawComment* b) const {
+ return this->operator()(a, b->getBeginLoc());
+ }
+ const bool operator()(const clang::RawComment* a,
+ const clang::RawComment* b) const {
+ return this->operator()(a->getBeginLoc(), b->getBeginLoc());
+ }
+ using OrderedItemId = std::tuple<clang::SourceRange, int, ItemId>;
using OrderedItem = std::tuple<clang::SourceRange, int, IR::Item>;
- clang::SourceManager& sm = ctx_.getSourceManager();
- auto is_less_than = [&sm](const OrderedItem& a, const OrderedItem& b) {
+
+ template <typename OrderedItemOrId>
+ bool operator()(const OrderedItemOrId& a, const OrderedItemOrId& b) const {
auto a_range = std::get<0>(a);
auto b_range = std::get<0>(b);
if (!a_range.isValid() || !b_range.isValid()) {
@@ -287,82 +258,138 @@
return sm.isBeforeInTranslationUnit(a_range.getEnd(), b_range.getEnd());
}
}
-
auto a_decl_order = std::get<1>(a);
auto b_decl_order = std::get<1>(b);
- if (a_decl_order != b_decl_order) return a_decl_order < b_decl_order;
+ return a_decl_order < b_decl_order;
+ }
- // A single FunctionDecl can be associated with multiple UnsupportedItems.
- // Comparing the fields allows deterministic order between items like:
- // Non-trivial_abi type '...' is not supported by value as a parameter.
- // Non-trivial_abi type '...' is not supported by value as a return type.
- const auto& a_variant = std::get<2>(a);
- const auto& b_variant = std::get<2>(b);
- const auto* a_unsupported = std::get_if<UnsupportedItem>(&a_variant);
- const auto* b_unsupported = std::get_if<UnsupportedItem>(&b_variant);
- if (a_unsupported && b_unsupported) {
- if (a_unsupported->name != b_unsupported->name)
- return a_unsupported->name < b_unsupported->name;
- return a_unsupported->message < b_unsupported->message;
+ SourceLocationComparator(clang::SourceManager& sm) : sm(sm) {}
+
+ private:
+ clang::SourceManager& sm;
+};
+
+std::vector<ItemId> Importer::GetItemIdsInSourceOrder(
+ clang::Decl* parent_decl) {
+ auto decl_context = clang::cast<clang::DeclContext>(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) {
+ ordered_comments.insert({comment->getBeginLoc(), comment});
+ }
+
+ absl::flat_hash_set<ItemId> visited_item_ids;
+ for (auto child : decl_context->decls()) {
+ auto decl = child->getCanonicalDecl();
+ if (!IsFromCurrentTarget(decl)) continue;
+
+ // 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()));
- return false;
- };
- auto are_equal = [&is_less_than](const OrderedItem& a, const OrderedItem& b) {
- return !is_less_than(a, b) && !is_less_than(b, a);
- };
+ // We assume that all the decls of the given parameter decl are already
+ // imported. Some calls to ImportDecl() may have returned std::nullopt, and
+ // those decls don't have corresponding items in the ir, so we don't add
+ // their item id to the list.
+ auto item = import_cache_.find(decl);
+ CRUBIT_CHECK(item != import_cache_.end() && "Found non-imported decl");
+ if (!item->second.has_value()) {
+ continue;
+ }
+ 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({decl->getSourceRange(), GetDeclOrder(decl), item_id});
+ }
+ }
- // We emit IR items in the order of the decls they were generated for.
- // For decls that emit multiple items we use a stable, but arbitrary order.
- std::vector<OrderedItem> items;
+ for (auto& [_, comment] : ordered_comments) {
+ items.push_back({comment->getSourceRange(), 0, GenerateItemId(comment)});
+ }
+ std::sort(items.begin(), items.end(), compare_locations);
+
+ std::vector<ItemId> ordered_item_ids;
+ ordered_item_ids.reserve(items.size());
+ for (auto& ordered_item : items) {
+ ordered_item_ids.push_back(std::get<2>(ordered_item));
+ }
+ return ordered_item_ids;
+}
+
+void Importer::ImportFreeComments() {
+ clang::SourceManager& sm = ctx_.getSourceManager();
+ for (const auto& header : invocation_.entry_headers_) {
+ if (auto file = sm.getFileManager().getFile(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);
+ }
+ }
+ }
+ }
+ std::sort(comments_.begin(), comments_.end(), SourceLocationComparator(sm));
+}
+
+void Importer::Import(clang::TranslationUnitDecl* translation_unit_decl) {
+ ImportFreeComments();
+ clang::SourceManager& sm = ctx_.getSourceManager();
+ std::vector<SourceLocationComparator::OrderedItem> ordered_items;
+
+ for (auto& comment : comments_) {
+ ordered_items.push_back(
+ {comment->getSourceRange(), 0,
+ 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()) {
if (std::holds_alternative<UnsupportedItem>(*item) &&
!IsFromCurrentTarget(decl)) {
continue;
}
-
- items.push_back(
- std::make_tuple(decl->getSourceRange(), GetDeclOrder(decl), *item));
+ ordered_items.push_back(
+ {decl->getSourceRange(), GetDeclOrder(decl), *item});
}
}
- for (auto comment : ImportFreeComments()) {
- items.push_back(std::make_tuple(
- comment->getSourceRange(), 0 /* decl_order */,
- Comment{.text = comment->getFormattedText(sm, sm.getDiagnostics()),
- .id = GenerateItemId(comment)}));
- }
- std::sort(items.begin(), items.end(), is_less_than);
+ std::sort(ordered_items.begin(), ordered_items.end(),
+ SourceLocationComparator(sm));
- for (size_t i = 0; i < items.size(); i++) {
- const auto& item = items[i];
- if (i > 0) {
- const auto& prev = items[i - 1];
- if (are_equal(item, prev)) {
- llvm::json::Value prev_json = std::visit(
- [&](auto&& item) { return item.ToJson(); }, std::get<2>(prev));
- llvm::json::Value curr_json = std::visit(
- [&](auto&& item) { return item.ToJson(); }, std::get<2>(item));
- if (prev_json != curr_json) {
- llvm::report_fatal_error(
- llvm::formatv("Non-deterministic order of IR items: {0} -VS- {1}",
- prev_json, curr_json));
- } else {
- // TODO(lukasza): Avoid generating duplicate IR items. Currently
- // known example: UnsupportedItem: name=std::signbit; message=
- // Items contained in namespaces are not supported yet.
- continue;
- }
- }
- }
- invocation_.ir_.items.push_back(std::get<2>(item));
+ invocation_.ir_.items.reserve(ordered_items.size());
+ for (auto& ordered_item : ordered_items) {
+ invocation_.ir_.items.push_back(std::get<2>(ordered_item));
}
+ invocation_.ir_.top_level_item_ids =
+ GetItemIdsInSourceOrder(translation_unit_decl);
}
void Importer::ImportDeclsFromDeclContext(
const clang::DeclContext* decl_context) {
for (auto decl : decl_context->decls()) {
+ // TODO(rosica): We don't always want the canonical decl here (especially
+ // not in namespaces).
ImportDeclIfNeeded(decl->getCanonicalDecl());
}
}
@@ -382,7 +409,8 @@
return ImportFunction(function_decl);
} else if (auto* function_template_decl =
clang::dyn_cast<clang::FunctionTemplateDecl>(decl)) {
- return ImportFunction(function_template_decl->getTemplatedDecl());
+ return ImportFunction(function_template_decl->getTemplatedDecl(),
+ function_template_decl);
} else if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
auto result = ImportRecord(record_decl);
// TODO(forster): Should we even visit the nested decl if we couldn't
@@ -403,11 +431,12 @@
}
std::optional<IR::Item> Importer::ImportFunction(
- clang::FunctionDecl* function_decl) {
+ clang::FunctionDecl* function_decl,
+ clang::FunctionTemplateDecl* function_template_decl) {
if (!IsFromCurrentTarget(function_decl)) return std::nullopt;
if (function_decl->isDeleted()) return std::nullopt;
if (function_decl->isTemplated()) {
- return ImportUnsupportedItem(function_decl,
+ return ImportUnsupportedItem(function_template_decl,
"Function templates are not supported yet");
}
@@ -604,7 +633,9 @@
.member_func_metadata = std::move(member_func_metadata),
.has_c_calling_convention = has_c_calling_convention,
.source_loc = ConvertSourceLocation(function_decl->getBeginLoc()),
- .id = GenerateItemId(function_decl),
+ .id = function_template_decl == nullptr
+ ? GenerateItemId(function_decl)
+ : GenerateItemId(function_template_decl),
};
}
return std::nullopt;
@@ -716,6 +747,8 @@
}
}
+ ImportDeclsFromDeclContext(record_decl);
+ auto item_ids = GetItemIdsInSourceOrder(record_decl);
return Record{
.rs_name = std::string(record_name->Ident()),
.cc_name = std::string(record_name->Ident()),
@@ -734,7 +767,8 @@
.is_trivial_abi = record_decl->canPassInRegisters(),
.is_inheritable =
!record_decl->isEffectivelyFinal() && !record_decl->isUnion(),
- .is_union = record_decl->isUnion()};
+ .is_union = record_decl->isUnion(),
+ .child_item_ids = std::move(item_ids)};
}
std::optional<IR::Item> Importer::ImportEnum(clang::EnumDecl* enum_decl) {
diff --git a/rs_bindings_from_cc/importer.h b/rs_bindings_from_cc/importer.h
index f989e6e..d20197f 100644
--- a/rs_bindings_from_cc/importer.h
+++ b/rs_bindings_from_cc/importer.h
@@ -106,7 +106,9 @@
// These functions import specific `Decl` subtypes. They use `LookupDecl` to
// lookup dependencies. They don't use or update the cache themselves.
- std::optional<IR::Item> ImportFunction(clang::FunctionDecl* function_decl);
+ std::optional<IR::Item> ImportFunction(
+ clang::FunctionDecl* function_decl,
+ clang::FunctionTemplateDecl* function_template_decl = nullptr);
std::optional<IR::Item> ImportRecord(clang::CXXRecordDecl* record_decl);
std::optional<IR::Item> ImportTypedefName(
clang::TypedefNameDecl* typedef_name_decl);
@@ -118,7 +120,12 @@
absl::StatusOr<std::vector<Field>> ImportFields(
clang::CXXRecordDecl* record_decl);
- std::vector<clang::RawComment*> ImportFreeComments();
+ // Stores the comments of this target in source order.
+ void ImportFreeComments();
+ // Returns the item ids of the children and comments of the given decl in
+ // source order. This method assumes that the children decls have already been
+ // imported.
+ std::vector<ItemId> GetItemIdsInSourceOrder(clang::Decl* decl);
std::string GetMangledName(const clang::NamedDecl* named_decl) const;
BazelLabel GetOwningTarget(const clang::Decl* decl) const;
@@ -185,6 +192,7 @@
absl::flat_hash_map<const clang::Decl*, std::optional<IR::Item>>
import_cache_;
absl::flat_hash_set<const clang::TypeDecl*> known_type_decls_;
+ std::vector<const clang::RawComment*> comments_;
}; // class Importer
} // namespace crubit
diff --git a/rs_bindings_from_cc/importer_test.cc b/rs_bindings_from_cc/importer_test.cc
index 9541618..808f513 100644
--- a/rs_bindings_from_cc/importer_test.cc
+++ b/rs_bindings_from_cc/importer_test.cc
@@ -24,12 +24,14 @@
using ::testing::AllOf;
using ::testing::AnyOf;
+using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Pointee;
using ::testing::SizeIs;
+using ::testing::UnorderedElementsAre;
using ::testing::VariantWith;
using ::testing::status::StatusIs;
@@ -42,6 +44,23 @@
return std::nullopt;
}
+std::optional<IR::Item> FindItemById(const IR& ir, ItemId id) {
+ for (auto item : ir.items) {
+ if (auto* record = std::get_if<Record>(&item); record && record->id == id) {
+ return item;
+ } else if (auto* func = std::get_if<Func>(&item); func && func->id == id) {
+ return item;
+ } else if (auto* comment = std::get_if<Comment>(&item);
+ comment && comment->id == id) {
+ return item;
+ } else if (auto* unsupported = std::get_if<UnsupportedItem>(&item);
+ unsupported && unsupported->id == id) {
+ return item;
+ }
+ }
+ return std::nullopt;
+}
+
template <typename T>
UnqualifiedIdentifier GetName(const T& x) {
return x.identifier;
@@ -111,6 +130,14 @@
return false;
}
+// Matches text for comments.
+MATCHER_P(TextIs, text, "") {
+ if (arg.text == text) return true;
+
+ *result_listener << "actual text: '" << arg.text << "'";
+ return false;
+}
+
// Matches an RsType or CcType that has the given decl_id.
MATCHER_P(DeclIdIs, decl_id, "") {
if (arg.decl_id.hasValue() && *arg.decl_id == decl_id) return true;
@@ -301,7 +328,7 @@
TEST(ImporterTest, FuncWithVoidReturnType) {
ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("void Foo();"));
EXPECT_THAT(ItemsWithoutBuiltins(ir),
- ElementsAre(VariantWith<Func>(
+ UnorderedElementsAre(VariantWith<Func>(
AllOf(IdentifierIs("Foo"), MangledNameIs("_Z3Foov"),
ReturnType(IsVoid()), ParamsAre()))));
}
@@ -310,7 +337,7 @@
ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("void Foo(); void Bar();"));
EXPECT_THAT(
ItemsWithoutBuiltins(ir),
- ElementsAre(
+ UnorderedElementsAre(
VariantWith<Func>(AllOf(IdentifierIs("Foo"), MangledNameIs("_Z3Foov"),
ReturnType(IsVoid()), ParamsAre())),
VariantWith<Func>(AllOf(IdentifierIs("Bar"), MangledNameIs("_Z3Barv"),
@@ -331,35 +358,36 @@
BazelLabel{"//two_funcs:one_target"}},
}));
EXPECT_THAT(ItemsWithoutBuiltins(ir),
- ElementsAre(VariantWith<Func>(IdentifierIs("Foo")),
- VariantWith<Func>(IdentifierIs("Bar"))));
+ UnorderedElementsAre(VariantWith<Func>(IdentifierIs("Foo")),
+ VariantWith<Func>(IdentifierIs("Bar"))));
}
TEST(ImporterTest, NonInlineFunc) {
ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("void Foo() {}"));
EXPECT_THAT(ItemsWithoutBuiltins(ir),
- ElementsAre(VariantWith<Func>(
+ UnorderedElementsAre(VariantWith<Func>(
AllOf(IdentifierIs("Foo"), Not(IsInline())))));
}
TEST(ImporterTest, InlineFunc) {
ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("inline void Foo() {}"));
- EXPECT_THAT(
- ItemsWithoutBuiltins(ir),
- ElementsAre(VariantWith<Func>(AllOf(IdentifierIs("Foo"), IsInline()))));
+ EXPECT_THAT(ItemsWithoutBuiltins(ir),
+ UnorderedElementsAre(
+ VariantWith<Func>(AllOf(IdentifierIs("Foo"), IsInline()))));
}
TEST(ImporterTest, FuncJustOnce) {
ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("void Foo(); void Foo();"));
- EXPECT_THAT(ItemsWithoutBuiltins(ir),
- ElementsAre(VariantWith<Func>(AllOf(IdentifierIs("Foo")))));
+ EXPECT_THAT(
+ ItemsWithoutBuiltins(ir),
+ UnorderedElementsAre(VariantWith<Func>(AllOf(IdentifierIs("Foo")))));
}
TEST(ImporterTest, TestImportPointerFunc) {
ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("int* Foo(int* a);"));
EXPECT_THAT(ItemsWithoutBuiltins(ir),
- ElementsAre(VariantWith<Func>(AllOf(
+ UnorderedElementsAre(VariantWith<Func>(AllOf(
ReturnType(IsIntPtr()), ParamsAre(ParamType(IsIntPtr()))))));
}
@@ -383,7 +411,7 @@
ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("int& Foo(int& a);"));
EXPECT_THAT(ItemsWithoutBuiltins(ir),
- ElementsAre(VariantWith<Func>(AllOf(
+ UnorderedElementsAre(VariantWith<Func>(AllOf(
ReturnType(IsIntRef()), ParamsAre(ParamType(IsIntRef()))))));
}
@@ -776,5 +804,73 @@
EXPECT_THAT(records, Each(Pointee(Not(IsTrivialAbi()))));
}
+TEST(ImporterTest, TopLevelItemIds) {
+ absl::string_view file = R"cc(
+ struct TopLevelStruct {};
+ // Top level comment
+
+ // Function comment
+ void top_level_func();
+ namespace top_level_namespace {
+ struct Nested {};
+ // free nested comment
+
+ // nested_func comment
+ void nested_func();
+ } // namespace top_level_namespace
+ )cc";
+ ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc(file));
+
+ std::vector<IR::Item> items;
+ for (const auto& id : ir.top_level_item_ids) {
+ auto item = FindItemById(ir, id);
+ ASSERT_TRUE(item.has_value());
+ items.push_back(*item);
+ }
+
+ EXPECT_THAT(ir.top_level_item_ids, SizeIs(5));
+ EXPECT_THAT(
+ items,
+ ElementsAre(
+ VariantWith<Record>(RsNameIs("TopLevelStruct")),
+ VariantWith<Comment>(TextIs("Top level comment")),
+ VariantWith<Func>(IdentifierIs("top_level_func")),
+ VariantWith<UnsupportedItem>(NameIs("top_level_namespace")),
+ VariantWith<Comment>(TextIs("namespace top_level_namespace"))));
+}
+
+TEST(ImporterTest, RecordItemIds) {
+ absl::string_view file = R"cc(
+ struct TopLevelStruct {
+ // A free comment
+
+ // foo comment
+ int foo;
+
+ int bar();
+ struct Nested {};
+ int baz();
+ };
+ )cc";
+ ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc(file));
+
+ std::vector<const Record*> records = ir.get_items_if<Record>();
+ EXPECT_THAT(records, SizeIs(1));
+
+ std::vector<IR::Item> items;
+ for (const auto& id : records[0]->child_item_ids) {
+ auto item = FindItemById(ir, id);
+ ASSERT_TRUE(item.has_value());
+ items.push_back(*item);
+ }
+
+ EXPECT_THAT(items,
+ AllOf(Contains(VariantWith<Comment>(TextIs("A free comment"))),
+ Contains(VariantWith<Func>(IdentifierIs("bar"))),
+ Contains(VariantWith<UnsupportedItem>(
+ NameIs("TopLevelStruct::Nested"))),
+ Contains(VariantWith<Func>(IdentifierIs("baz")))));
+}
+
} // namespace
} // namespace crubit
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 3d9d8a5..675d122 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -360,6 +360,12 @@
}
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},
@@ -379,6 +385,7 @@
{"is_trivial_abi", is_trivial_abi},
{"is_inheritable", is_inheritable},
{"is_union", is_union},
+ {"child_item_ids", std::move(json_item_ids)},
};
return llvm::json::Object{
@@ -443,7 +450,7 @@
{"text", text},
{"id", id},
};
-
+ comment["id"] = id.value();
return llvm::json::Object{
{"Comment", std::move(comment)},
};
@@ -456,10 +463,17 @@
std::visit([&](auto&& item) { json_items.push_back(item.ToJson()); }, item);
}
+ 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());
+ }
+
return llvm::json::Object{
{"used_headers", used_headers},
{"current_target", current_target},
{"items", std::move(json_items)},
+ {"top_level_item_ids", std::move(top_level_ids)},
};
}
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index aeae4d1..2936899 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -571,6 +571,8 @@
// Whether this type is a C++ union (rather than a struct)
bool is_union = false;
+
+ std::vector<ItemId> child_item_ids;
};
struct Enumerator {
@@ -665,6 +667,7 @@
using Item =
std::variant<Func, Record, Enum, TypeAlias, UnsupportedItem, Comment>;
std::vector<Item> items;
+ std::vector<ItemId> top_level_item_ids;
};
inline std::ostream& operator<<(std::ostream& o, const IR& ir) {
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 2c9c684..0ccdebc 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -29,6 +29,7 @@
items.into_iter().collect_vec(),
/* used_headers= */ vec![],
/* current_target= */ TESTING_TARGET.into(),
+ /* top_level_item_ids= */ vec![],
)
}
@@ -38,8 +39,9 @@
items: Vec<Item>,
used_headers: Vec<HeaderName>,
current_target: BazelLabel,
+ top_level_item_ids: Vec<ItemId>,
) -> Result<IR> {
- make_ir(FlatIR { used_headers, current_target, items })
+ make_ir(FlatIR { used_headers, current_target, items, top_level_item_ids })
}
fn make_ir(flat_ir: FlatIR) -> Result<IR> {
@@ -365,6 +367,7 @@
pub is_trivial_abi: bool,
pub is_inheritable: bool,
pub is_union: bool,
+ pub child_item_ids: Vec<ItemId>,
}
impl Record {
@@ -542,6 +545,8 @@
current_target: BazelLabel,
#[serde(default)]
items: Vec<Item>,
+ #[serde(default)]
+ top_level_item_ids: Vec<ItemId>,
}
/// Struct providing the necessary information about the API of a C++ target to
@@ -560,6 +565,10 @@
self.flat_ir.items.iter()
}
+ pub fn top_level_item_ids(&self) -> impl Iterator<Item = &ItemId> {
+ self.flat_ir.top_level_item_ids.iter()
+ }
+
pub fn items_mut(&mut self) -> impl Iterator<Item = &mut Item> {
self.flat_ir.items.iter_mut()
}
@@ -707,6 +716,7 @@
let expected = FlatIR {
used_headers: vec![HeaderName { name: "foo/bar.h".to_string() }],
current_target: "//foo:bar".into(),
+ top_level_item_ids: vec![],
items: vec![],
};
assert_eq!(ir.flat_ir, expected);
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 13a1daf..96d0443 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -10,7 +10,7 @@
use quote::quote;
use std::collections::{HashMap, HashSet};
use std::iter::Iterator;
-use token_stream_matchers::{assert_ir_matches, assert_ir_not_matches};
+use token_stream_matchers::{assert_ir_matches, assert_ir_not_matches, assert_items_match};
#[test]
fn test_function() {
@@ -831,6 +831,7 @@
base_size: Some(4),
override_alignment: true,
...
+ ...
}),
}
);
@@ -1463,3 +1464,117 @@
ir.functions().find(|i| i.name == UnqualifiedIdentifier::Identifier(ir_id("foo"))).unwrap();
assert_ne!(function.id, ItemId(0));
}
+
+#[test]
+fn test_top_level_items() {
+ let ir = ir_from_cc(
+ r#"
+ struct TopLevelStruct {};
+ // Top level comment
+
+ // Function comment
+ void top_level_func();
+ namespace top_level_namespace {
+ struct Nested {};
+ // free nested comment
+
+ // nested_func comment
+ void nested_func();
+ } // namespace top_level_namespace"#,
+ )
+ .unwrap();
+
+ let top_level_items =
+ ir.top_level_item_ids().map(|id| ir.find_decl(*id).unwrap()).collect_vec();
+
+ assert_items_match!(
+ top_level_items,
+ vec![
+ quote! {
+ Record {
+ ... rs_name: "TopLevelStruct" ...
+ }
+ },
+ quote! {
+ Comment {
+ ... text: "Top level comment" ...
+ }
+ },
+ quote! {
+ Func { ... name: "top_level_func" ... }
+ },
+ quote! {
+ UnsupportedItem { ... name: "top_level_namespace" ... }
+ },
+ quote! {
+ Comment {
+ ... text: "namespace top_level_namespace" ...
+ }
+ },
+ ]
+ );
+}
+
+#[test]
+fn test_record_items() {
+ let ir = ir_from_cc(
+ r#"
+ struct TopLevelStruct {
+ // A free comment
+
+ // foo comment
+ int foo;
+
+ int bar();
+ struct Nested {};
+ int baz();
+ };"#,
+ )
+ .unwrap();
+
+ let record = ir.records().find(|i| i.rs_name.as_str() == "TopLevelStruct").unwrap();
+ let record_items =
+ record.child_item_ids.iter().map(|id| ir.find_decl(*id).unwrap()).collect_vec();
+
+ assert_items_match!(
+ record_items,
+ vec![
+ quote! {
+ Func { ... name: Constructor ... }
+ },
+ quote! {
+ Func { ... name: Constructor ... }
+ },
+ quote! {
+ // Unsupported parameter
+ UnsupportedItem { ... name: "TopLevelStruct::TopLevelStruct", ... }
+ },
+ quote! {
+ Func { ... name: Destructor ... }
+ },
+ quote! {
+ Func { ... name: "operator=" ... }
+ },
+ quote! {
+ // Unsupported parameter
+ UnsupportedItem { ... name: "TopLevelStruct::operator=" ... }
+ },
+ quote! {
+ ...Comment {
+ ... text: "A free comment" ...
+ }
+ },
+ quote! {
+ ... Func { ... name: "bar" ... }
+ },
+ quote! {
+ ... UnsupportedItem { ... name: "TopLevelStruct::Nested" ... }
+ },
+ quote! {
+ ...Func {
+ ... name: "baz" ...
+ }
+ },
+ ]
+ );
+}
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 44b46cb..0060716 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -837,7 +837,11 @@
/// Generates Rust source code for a given `Record` and associated assertions as
/// a tuple.
-fn generate_record(record: &Record, ir: &IR) -> Result<(RsSnippet, RsSnippet)> {
+fn generate_record(
+ record: &Record,
+ ir: &IR,
+ overloaded_funcs: &HashSet<FunctionId>,
+) -> Result<GeneratedItem> {
let ident = make_rs_ident(&record.rs_name);
let doc_comment = generate_doc_comment(&record.doc_comment);
let field_idents =
@@ -975,6 +979,30 @@
let no_unique_address_accessors = cc_struct_no_unique_address_impl(record, ir)?;
let base_class_into = cc_struct_upcast_impl(record, ir)?;
+ let record_generated_items = record
+ .child_item_ids
+ .iter()
+ .map(|id| {
+ let item = ir.find_decl(*id)?;
+ generate_item(item, ir, overloaded_funcs)
+ })
+ .collect::<Result<Vec<_>>>()?;
+
+ let mut items = vec![];
+ let mut thunks_from_record_items = vec![];
+ let mut assertions_from_record_items = vec![];
+
+ for generated in record_generated_items.iter() {
+ items.push(&generated.item);
+ if !generated.thunks.is_empty() {
+ thunks_from_record_items.push(&generated.thunks);
+ }
+ if !generated.assertions.is_empty() {
+ assertions_from_record_items.push(&generated.assertions);
+ }
+ record_features.extend(generated.features.clone());
+ }
+
let record_tokens = quote! {
#doc_comment
#derives
@@ -990,6 +1018,9 @@
#base_class_into
#unpin_impl
+
+ __NEWLINE__ __NEWLINE__
+ #( #items __NEWLINE__ __NEWLINE__)*
};
let record_trait_assertions = {
@@ -1024,12 +1055,20 @@
#( #record_trait_assertions )*
#( #field_offset_assertions )*
#( #field_copy_trait_assertions )*
+ #( #assertions_from_record_items )*
};
- Ok((
- RsSnippet { features: record_features, tokens: record_tokens },
- RsSnippet { features: assertion_features, tokens: assertion_tokens },
- ))
+ let thunk_tokens = quote! {
+ #( #thunks_from_record_items )*
+ };
+
+ Ok(GeneratedItem {
+ item: record_tokens,
+ features: record_features.union(&assertion_features).cloned().collect(),
+ assertions: assertion_tokens,
+ thunks: thunk_tokens,
+ has_record: true,
+ })
}
fn should_derive_clone(record: &Record) -> bool {
@@ -1162,18 +1201,7 @@
{
GeneratedItem { ..Default::default() }
} else {
- let (snippet, assertions_snippet) = generate_record(record, ir)?;
- GeneratedItem {
- item: snippet.tokens,
- assertions: assertions_snippet.tokens,
- features: snippet
- .features
- .union(&assertions_snippet.features)
- .cloned()
- .collect(),
- has_record: true,
- ..Default::default()
- }
+ generate_record(record, ir, overloaded_funcs)?
}
}
Item::Enum(enum_) => {
@@ -1239,7 +1267,8 @@
}
}
- for item in ir.items() {
+ for top_level_item_id in ir.top_level_item_ids() {
+ let item = ir.find_decl(*top_level_item_id)?;
let generated = generate_item(item, ir, &overloaded_funcs)?;
items.push(generated.item);
if !generated.thunks.is_empty() {
diff --git a/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs b/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs
index 5b9fc16..4745bc5 100644
--- a/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/comment.h b/rs_bindings_from_cc/test/golden/comment.h
index 241af47..d290c0c 100644
--- a/rs_bindings_from_cc/test/golden/comment.h
+++ b/rs_bindings_from_cc/test/golden/comment.h
@@ -15,17 +15,20 @@
/// Foo
struct Foo final {
- // Foo a
+ // TODO(rosica): This comment appears near fields of a struct, and
+ // is currently generated below the struct definiton on the Rust side.
/// A field
int i;
- // Foo b
+ // TODO(rosica): This comment appears between fields of a struct, and
+ // is currently generated below the struct definiton on the Rust side.
/// Another field
int j;
- // Foo c
+ // TODO(rosica): This comment appears near fields of a struct, and
+ // is currently generated below the struct definiton on the Rust side.
};
// b
diff --git a/rs_bindings_from_cc/test/golden/comment_rs_api.rs b/rs_bindings_from_cc/test/golden/comment_rs_api.rs
index 3ce9927..f6a94e1 100644
--- a/rs_bindings_from_cc/test/golden/comment_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/comment_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
@@ -65,6 +63,15 @@
// Error while generating bindings for item 'Foo::operator=':
// Bindings for this kind of operator are not supported
+// TODO(rosica): This comment appears near fields of a struct, and
+// is currently generated below the struct definiton on the Rust side.
+
+// TODO(rosica): This comment appears between fields of a struct, and
+// is currently generated below the struct definiton on the Rust side.
+
+// TODO(rosica): This comment appears near fields of a struct, and
+// is currently generated below the struct definiton on the Rust side.
+
// b
// } // namespace ns
@@ -106,11 +113,11 @@
}
}
-// rs_bindings_from_cc/test/golden/comment.h;l=43
+// rs_bindings_from_cc/test/golden/comment.h;l=46
// Error while generating bindings for item 'Bar::operator=':
// Bindings for this kind of operator are not supported
-// rs_bindings_from_cc/test/golden/comment.h;l=43
+// rs_bindings_from_cc/test/golden/comment.h;l=46
// Error while generating bindings for item 'Bar::operator=':
// Bindings for this kind of operator are not supported
@@ -143,11 +150,11 @@
}
}
-// rs_bindings_from_cc/test/golden/comment.h;l=49
+// rs_bindings_from_cc/test/golden/comment.h;l=52
// Error while generating bindings for item 'HasNoComments::operator=':
// Bindings for this kind of operator are not supported
-// rs_bindings_from_cc/test/golden/comment.h;l=49
+// rs_bindings_from_cc/test/golden/comment.h;l=52
// Error while generating bindings for item 'HasNoComments::operator=':
// Bindings for this kind of operator are not supported
diff --git a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
index 3de6bc3..8506741 100644
--- a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs b/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs
index be9558b..ced4f4a 100644
--- a/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
@@ -64,7 +62,7 @@
// Error while generating bindings for item 'await':
// Class templates are not supported yet
-// rs_bindings_from_cc/test/golden/escaping_keywords.h;l=22
+// rs_bindings_from_cc/test/golden/escaping_keywords.h;l=21
// Error while generating bindings for item 'become':
// Function templates are not supported yet
diff --git a/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs b/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs
index 8f5d692..3522e20 100644
--- a/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/item_order_rs_api.rs b/rs_bindings_from_cc/test/golden/item_order_rs_api.rs
index c5101af..445e018 100644
--- a/rs_bindings_from_cc/test/golden/item_order_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/item_order_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/lifetimes_rs_api.rs b/rs_bindings_from_cc/test/golden/lifetimes_rs_api.rs
index 3d82054..9715b34 100644
--- a/rs_bindings_from_cc/test/golden/lifetimes_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/lifetimes_rs_api.rs
@@ -11,8 +11,6 @@
use ::std as rust_std;
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs b/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs
index 6709bdb..2d60078 100644
--- a/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs b/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
index f5a4fe4..4de66ae 100644
--- a/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs b/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs
index 848089c..daf2907 100644
--- a/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/private_members_rs_api.rs b/rs_bindings_from_cc/test/golden/private_members_rs_api.rs
index e71d8c8..c6b3703 100644
--- a/rs_bindings_from_cc/test/golden/private_members_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/private_members_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs b/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs
index 11c82eb..32f251b 100644
--- a/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs b/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs
index 968c268..50212c5 100644
--- a/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/types_rs_api.rs b/rs_bindings_from_cc/test/golden/types_rs_api.rs
index 8182540..d678482 100644
--- a/rs_bindings_from_cc/test/golden/types_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/types_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
@@ -160,6 +158,10 @@
}
}
+// TODO(b/226580208): Uncomment when these don't cause struct import to fail.
+// SomeStruct&& struct_rvalue_ref_field;
+// const SomeStruct&& const_struct_rvalue_ref_field;
+
#[derive(Clone, Copy)]
#[repr(C)]
pub union NonEmptyUnion {
diff --git a/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs b/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
index 449c754..132d849 100644
--- a/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
@@ -104,14 +102,6 @@
// Error while generating bindings for item 'ContainingStruct::NestedStruct':
// Nested classes are not supported yet
-// rs_bindings_from_cc/test/golden/unsupported.h;l=32
-// Error while generating bindings for item 'ContainingStruct::NestedStruct::NonStaticMemberFunction':
-// Couldn't import the parent
-
-// rs_bindings_from_cc/test/golden/unsupported.h;l=33
-// Error while generating bindings for item 'ContainingStruct::NestedStruct::StaticMemberFunction':
-// Couldn't import the parent
-
// CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNSUPPORTED_H_
mod detail {
diff --git a/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs b/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs
index 1a01da6..af9f2ad 100644
--- a/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs b/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
index 263beda..0d7d3dc 100644
--- a/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
@@ -13,8 +13,6 @@
use memoffset_unstable_const::offset_of;
use static_assertions::{assert_impl_all, assert_not_impl_all};
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs b/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs
index 0ebd231..1ca30dd 100644
--- a/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs
@@ -11,8 +11,6 @@
use ::std as rust_std;
-pub type __builtin_ms_va_list = *mut u8;
-
// 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
diff --git a/rs_bindings_from_cc/token_stream_matchers.rs b/rs_bindings_from_cc/token_stream_matchers.rs
index 62e1333..bdcb6c0 100644
--- a/rs_bindings_from_cc/token_stream_matchers.rs
+++ b/rs_bindings_from_cc/token_stream_matchers.rs
@@ -104,11 +104,30 @@
};
}
+/// Like `assert_ir_matches!`, but expects a list of Items and a list of
+/// TokenStreams as input. The macro converts each Item to its struct expression
+/// and matches the corresponding pattern against that.
+/// The use case for this macro is to compare a list of Items to expected
+/// patterns, e.g when we want to confirm that the children items of an item
+/// appear in a certain order.
+#[macro_export]
+macro_rules! assert_items_match {
+ ($items:expr, $patterns:expr $(,)*) => {
+ assert_eq!($items.len(), $patterns.len());
+ for (idx, (item, pattern)) in itertools::enumerate(itertools::zip($items, $patterns)) {
+ $crate::internal::match_item(&item, &pattern).expect(&format!(
+ "input at position {} unexpectedly didn't match the pattern",
+ &idx
+ ));
+ }
+ };
+}
+
/// Only used to make stuff needed by exported macros available
pub mod internal {
use anyhow::{anyhow, Result};
- use ir::IR;
+ use ir::{Item, IR};
use itertools::Itertools;
pub use proc_macro2::TokenStream;
use proc_macro2::TokenTree;
@@ -150,6 +169,19 @@
Ok(snippet)
}
+ pub fn match_item(item: &Item, pattern: &TokenStream) -> Result<()> {
+ match_tokens(&item_to_token_stream(item)?, pattern, &ir_to_string)
+ }
+
+ fn item_to_token_stream(item: &Item) -> Result<TokenStream> {
+ // derived debug impl doesn't emit commas after the last element of a group,
+ // (for example `[a, b, c]`), but rustfmt automatically adds it (`[a, b,
+ // c,]`). We use rustfmt to format the failure messages. So to make the
+ // input token stream consistent with failure messages we format the
+ // input token stream with rustfmt as well.
+ Ok(ir_to_string(format! {"{:?}", item}.parse().unwrap())?.parse().unwrap())
+ }
+
#[derive(Debug)]
enum MatchInfo {
// Successful match with the suffix of the `input` stream that follows the match.
@@ -470,7 +502,10 @@
#[test]
fn test_assert_ir_matches_assumes_trailing_commas_in_groups() {
- assert_ir_matches!(ir_from_cc("").unwrap(), quote! {{... items: [...],}});
+ assert_ir_matches!(
+ ir_from_cc("").unwrap(),
+ quote! {{... items: [...], top_level_item_ids: [...], }}
+ );
}
#[test]