blob: 12d49e4f4b76bdf241a19dc5af1291bea36dd627 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "rs_bindings_from_cc/collect_namespaces.h"
#include <string>
#include <utility>
#include <vector>
#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
#include "llvm/Support/JSON.h"
namespace crubit {
namespace {
// A Trie that stores the namespace hierarchy.
//
// Consider the following namespace structure:
// namespace top_level_one {
// namespace internal {}
// } // namespace A
//
// namespace top_level_two {
// namespace internal {}
// }
//
// namespace top_level_one {
// namespace inner {}
// }
//
// A Namespace trie allows us to convert the above to:
//
// top_level_one -> [internal, inner]
// top_level_two -> [internal]
//
// Which we can then serialize to JSON.
class NamespaceTrie {
private:
struct Node {
absl::string_view name;
// A map of child namespace name to the index of the namespace node in the
// trie_nodes_ vector. We use a btree_map so that at conversion time we get
// deterministic JSON output.
absl::btree_map<absl::string_view, int> child_name_to_idx;
};
std::vector<Node> trie_nodes_;
// A map of top level namespace name to the index of the namespace node in the
// trie_nodes_ vector. We use a btree_map so that at conversion time we get
// deterministic JSON output.
absl::btree_map<absl::string_view, int> top_level_name_to_idx_;
// The current target's label.
BazelLabel label_;
// A map of item id to the IR Namespace item. It allows us to look up the
// children namespace items.
absl::flat_hash_map<ItemId, const Namespace*>& id_to_namespace_;
// Creates a node from a Namespace and inserts it into the trie.
void InsertNode(int parent_idx, const Namespace* ns) {
auto name = ns->name.Ident();
auto parent = &trie_nodes_[parent_idx];
int child_idx;
if (parent->child_name_to_idx.find(name) ==
parent->child_name_to_idx.end()) {
child_idx = trie_nodes_.size();
parent->child_name_to_idx.insert({name, child_idx});
// The following line potentially invalidates the "parent" pointer.
trie_nodes_.push_back({name, {}});
} else {
child_idx = parent->child_name_to_idx[name];
}
for (auto ns_child_id : ns->child_item_ids) {
if (id_to_namespace_.find(ns_child_id) == id_to_namespace_.end()) {
continue;
}
auto ns_child = id_to_namespace_[ns_child_id];
InsertNode(child_idx, ns_child);
}
}
// Converts a trie node into the JSON serializable NamespaceNode.
NamespaceNode NodeToNamespaceNode(const Node* node) const {
std::vector<NamespaceNode> namespaces;
namespaces.reserve(node->child_name_to_idx.size());
for (const auto& [_, idx] : node->child_name_to_idx) {
namespaces.push_back(NodeToNamespaceNode(&trie_nodes_[idx]));
}
return NamespaceNode{std::string(node->name), std::move(namespaces)};
}
public:
NamespaceTrie(BazelLabel label,
absl::flat_hash_map<ItemId, const Namespace*>& id_to_namespace)
: label_(label), id_to_namespace_(id_to_namespace) {}
NamespaceTrie(NamespaceTrie&) = delete;
NamespaceTrie& operator=(NamespaceTrie&) = delete;
// Creates a trie node from the top level namespace and inserts it into the
// trie.
void InsertTopLevel(const Namespace* ns) {
auto name = ns->name.Ident();
int node_idx;
if (top_level_name_to_idx_.find(name) == top_level_name_to_idx_.end()) {
node_idx = trie_nodes_.size();
top_level_name_to_idx_.insert({name, node_idx});
trie_nodes_.push_back({name, {}});
} else {
node_idx = top_level_name_to_idx_[name];
}
for (auto ns_child_id : ns->child_item_ids) {
if (id_to_namespace_.find(ns_child_id) == id_to_namespace_.end()) {
continue;
}
auto ns_child = id_to_namespace_[ns_child_id];
InsertNode(node_idx, ns_child);
}
}
// Converts the trie into the JSON serializable NamespacesHierarchy.
NamespacesHierarchy ToNamespacesHierarchy() {
std::vector<NamespaceNode> namespaces;
namespaces.reserve(this->top_level_name_to_idx_.size());
for (auto& [_, idx] : this->top_level_name_to_idx_) {
namespaces.push_back(NodeToNamespaceNode(&trie_nodes_[idx]));
}
return NamespacesHierarchy{label_, std::move(namespaces)};
}
};
} // namespace
// Returns the current target's namespace hierarchy in JSON serializable format.
NamespacesHierarchy CollectNamespaces(const IR& ir) {
auto all_namespaces = ir.get_items_if<Namespace>();
absl::flat_hash_map<ItemId, const Namespace*> id_to_namespace;
for (auto ns : all_namespaces) {
// We are not interested in namespaces from different targets.
if (ns->owning_target != ir.current_target) {
continue;
}
id_to_namespace.insert({ns->id, ns});
}
NamespaceTrie trie(ir.current_target, id_to_namespace);
for (auto namespace_id : ir.top_level_item_ids) {
if (id_to_namespace.count(namespace_id) == 0) {
continue;
}
auto ns = id_to_namespace[namespace_id];
trie.InsertTopLevel(ns);
}
return trie.ToNamespacesHierarchy();
}
llvm::json::Value NamespaceNode::ToJson() const {
std::vector<llvm::json::Value> json_children;
json_children.reserve(children.size());
for (const auto& child : children) {
json_children.push_back(child.ToJson());
}
return llvm::json::Object{
{"name", name},
{"children", std::move(json_children)},
};
}
llvm::json::Value NamespacesHierarchy::ToJson() const {
std::vector<llvm::json::Value> json_namespaces;
json_namespaces.reserve(namespaces.size());
for (const auto& ns : namespaces) {
json_namespaces.push_back(ns.ToJson());
}
return llvm::json::Object{
{"label", label.value()},
{"namespaces", std::move(json_namespaces)},
};
}
} // namespace crubit