blob: a80adacd773324f8c4a43f9a34d1cf9f4176b105 [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
use import_internal::{AbsoluteLabel, Mode};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use serde::Deserialize;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::rc::Rc;
use syn::Ident;
/// Representation of a target's namespace hierarchy, as encoded in the
/// *_namespace.json file.
#[derive(Clone, Debug, Deserialize)]
pub struct JsonNamespaceHierarchy {
pub label: Rc<str>,
#[serde(default)]
pub namespaces: Vec<JsonNamespace>,
}
/// Single C++ namespace, as encoded in the *_namespace.json file.
#[derive(Clone, Debug, Deserialize)]
pub struct JsonNamespace {
pub name: Rc<str>,
#[serde(default)]
pub children: Vec<JsonNamespace>,
}
/// A trie-like data structure that represents the merged namespace hierarchy
/// from all the direct dependencies.
#[derive(Clone, Debug)]
pub struct MergedNamespaceHierarchy {
/// A map of a top level namespace name to the corresponding
/// MergedNamespace.
pub top_level_namespaces: BTreeMap<Rc<str>, MergedNamespace>,
}
/// A single C++ namespace potentially reopened across multiple targets.
#[derive(Clone, Debug)]
pub struct MergedNamespace {
/// name of the current namespace.
pub name: Rc<str>,
/// A map of child namespace name to the corresponding MergedNamespace.
pub children: BTreeMap<Rc<str>, MergedNamespace>,
/// A set of targets that reopen this namespace.
pub labels: BTreeSet<Rc<str>>,
}
impl MergedNamespace {
/// Creates a MergedNamespace from JsonNamespace.
pub fn from_json_namespace(label: Rc<str>, json_namespace: &JsonNamespace) -> MergedNamespace {
let mut merged_children = BTreeMap::new();
for child in json_namespace.children.iter() {
merged_children.insert(
child.name.clone(),
MergedNamespace::from_json_namespace(label.clone(), child),
);
}
MergedNamespace {
name: json_namespace.name.clone(),
children: merged_children,
labels: BTreeSet::from([label]),
}
}
/// Merges the namespace passed as an argument into the current one.
pub(crate) fn merge(&mut self, other: MergedNamespace) {
let MergedNamespace { name, children, mut labels } = other;
assert!(
self.name == name,
"Cannot merge namespaces with different names, got '{}' and '{}'",
&self.name,
&name
);
self.labels.append(&mut labels);
for namespace in children.into_values() {
self.add_child(namespace);
}
}
fn add_child(&mut self, namespace: MergedNamespace) {
self.labels.append(&mut namespace.labels.iter().cloned().collect());
match self.children.get_mut(&namespace.name) {
Some(child_namespace) => {
let MergedNamespace { name: _, children, labels: _ } = namespace;
for child in children.into_values() {
child_namespace.add_child(child);
}
}
None => {
self.children.insert(namespace.name.clone(), namespace);
}
}
}
}
impl MergedNamespaceHierarchy {
/// Creates a MergedNamespaceHierarchy from JsonNamespaceHierarchy.
pub fn from_json_namespace_hierarchy(jnh: &JsonNamespaceHierarchy) -> MergedNamespaceHierarchy {
let mut merged_namespaces = BTreeMap::new();
for top_level_namespace in jnh.namespaces.iter() {
merged_namespaces.insert(
top_level_namespace.name.clone(),
MergedNamespace::from_json_namespace(jnh.label.clone(), top_level_namespace),
);
}
MergedNamespaceHierarchy { top_level_namespaces: merged_namespaces }
}
// Merges the namespace hierarchy passed as an argument into the current one.
pub fn merge(&mut self, other: MergedNamespaceHierarchy) {
for (name, namespace) in other.top_level_namespaces {
match self.top_level_namespaces.get_mut(&name) {
Some(child_namespace) => {
child_namespace.merge(namespace);
}
None => {
self.top_level_namespaces.insert(name, namespace);
}
}
}
}
}
impl ToTokens for MergedNamespaceHierarchy {
fn to_tokens(&self, tokens: &mut TokenStream) {
for namespace in self.top_level_namespaces.values() {
to_use_stmts(namespace, tokens, &mut vec![]);
}
}
}
fn to_use_stmts(
namespace: &MergedNamespace,
tokens: &mut TokenStream,
outer_namespaces: &mut Vec<Ident>,
) {
let module_name = Ident::new(&namespace.name, Span::call_site());
let mut inner_tokens = TokenStream::new();
for label_literal in namespace.labels.iter() {
let span = Span::call_site();
let label = AbsoluteLabel::parse(label_literal, &span).expect("Couldn't parse label");
// TODO(rosica): Use mangled name for the crate.
let crate_name = &label.crate_name(&Mode::NoRenaming);
let crate_ident = Ident::new(crate_name, span);
inner_tokens.append_all(quote! {
pub use #crate_ident::#(#outer_namespaces::)*#module_name::*;
});
}
outer_namespaces.push(module_name);
for inner in namespace.children.values() {
to_use_stmts(inner, &mut inner_tokens, outer_namespaces);
}
let module_name = outer_namespaces.pop();
tokens.extend(quote! {
pub mod #module_name {
#inner_tokens
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Deref;
#[test]
fn from_json() {
let json = r#"{
"label": "//foo/bar:baz",
"namespaces": [
{
"name": "top_level",
"children": []
}
]
}"#;
let ns: JsonNamespaceHierarchy = serde_json::from_str(json).unwrap();
assert_eq!(&*ns.label, "//foo/bar:baz");
assert_eq!(ns.namespaces.len(), 1);
assert_eq!(&*ns.namespaces[0].name, "top_level");
}
#[test]
fn test_merge_namespace_hierarchies() {
let hierarchy_one: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:baz",
"namespaces": [
{
"name": "top_level_1",
"children": []
}
]
}"#,
)
.unwrap();
let hierarchy_two: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:xyz",
"namespaces": [
{
"name": "top_level_1",
"children": []
},
{
"name": "top_level_2",
"children": []
}
]
}"#,
)
.unwrap();
let mut merged_hierarchy =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_one);
let merged_hierarchy_two =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_two);
merged_hierarchy.merge(merged_hierarchy_two);
let MergedNamespaceHierarchy { top_level_namespaces } = merged_hierarchy;
assert_eq!(top_level_namespaces.len(), 2);
let MergedNamespace { name, children: _, labels } =
top_level_namespaces.get("top_level_1").unwrap();
assert_eq!(&**name, "top_level_1");
assert_eq!(
labels.iter().map(Deref::deref).collect::<Vec<_>>(),
["//foo/bar:baz", "//foo/bar:xyz"]
);
let MergedNamespace { name, children: _, labels } =
top_level_namespaces.get("top_level_2").unwrap();
assert_eq!(&**name, "top_level_2");
assert_eq!(labels.iter().map(Deref::deref).collect::<Vec<_>>(), ["//foo/bar:xyz"]);
}
#[test]
#[should_panic(expected = "Cannot merge namespaces with different names, got 'a' and 'b'")]
fn test_merge_different_namespaces() {
let mut namespace_one = MergedNamespace::from_json_namespace(
"//label1".into(),
&serde_json::from_str(
r#"{
"name": "a",
"children": []
}"#,
)
.unwrap(),
);
let namespace_two = MergedNamespace::from_json_namespace(
"//label2".into(),
&serde_json::from_str(
r#"{
"name": "b",
"children": []
}"#,
)
.unwrap(),
);
namespace_one.merge(namespace_two);
}
#[test]
fn test_merge_namespaces() {
let json_namespace_one: JsonNamespace = serde_json::from_str(
r#"{
"name": "a",
"children": [
{
"name": "b",
"children": []
},
{
"name": "c",
"children": []
}
]
}"#,
)
.unwrap();
let json_namespace_two: JsonNamespace = serde_json::from_str(
r#"{
"name": "a",
"children": [
{
"name": "c",
"children": [
{
"name": "d",
"children": []
}
]
},
{
"name": "d",
"children": []
}
]
}"#,
)
.unwrap();
let mut merged_namespace =
MergedNamespace::from_json_namespace("//:label1".into(), &json_namespace_one);
let merged_namespace_two =
MergedNamespace::from_json_namespace("//:label2".into(), &json_namespace_two);
merged_namespace.merge(merged_namespace_two);
let MergedNamespace { name, children, labels } = merged_namespace;
assert_eq!(&*name, "a");
assert_eq!(labels.iter().map(Deref::deref).collect::<Vec<_>>(), ["//:label1", "//:label2"]);
assert_eq!(children.keys().map(Deref::deref).collect::<Vec<_>>(), ["b", "c", "d"]);
let MergedNamespace { name: _, children: _, labels: b_labels } = children.get("b").unwrap();
// Namespace b only exists in the first target
assert_eq!(b_labels.iter().map(Deref::deref).collect::<Vec<_>>(), ["//:label1"]);
let MergedNamespace { name: _, children: children_c, labels: c_labels } =
children.get("c").unwrap();
// Namespace c exists in both targets, however its child d only exists in the
// second target
assert_eq!(
c_labels.iter().map(Deref::deref).collect::<Vec<_>>(),
["//:label1", "//:label2"]
);
let MergedNamespace { name: _, children: _, labels: c_child_labels } =
children_c.get("d").unwrap();
assert_eq!(c_child_labels.iter().map(Deref::deref).collect::<Vec<_>>(), ["//:label2"]);
let MergedNamespace { name: _, children: _, labels: d_labels } = children.get("d").unwrap();
// Namespace d only exists in the second target
assert_eq!(d_labels.iter().map(Deref::deref).collect::<Vec<_>>(), ["//:label2"]);
}
#[test]
fn test_to_tokens() {
let hierarchy_one: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:baz",
"namespaces": [
{
"name": "top_level_1",
"children": [
{
"name": "reopened",
"children": [
{
"name": "baz_specific",
"children": []
}
]
}
]
}
]
}"#,
)
.unwrap();
let hierarchy_two: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:xyz",
"namespaces": [
{
"name": "top_level_1",
"children": [
{
"name": "xyz_specific",
"children": []
},
{
"name": "reopened",
"children": []
}
]
},
{
"name": "top_level_2",
"children": []
}
]
}"#,
)
.unwrap();
let mut merged_hierarchy =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_one);
let merged_hierarchy_two =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_two);
merged_hierarchy.merge(merged_hierarchy_two);
token_stream_matchers::assert_rs_matches!(
quote! {#merged_hierarchy},
quote! {
pub mod top_level_1 {
pub use baz::top_level_1::*;
pub use xyz::top_level_1::*;
pub mod reopened {
pub use baz::top_level_1::reopened::*;
pub mod baz_specific {
pub use baz::top_level_1::reopened::baz_specific::*;
}
}
pub mod xyz_specific {
pub use xyz::top_level_1::xyz_specific::*;
}
}
pub mod top_level_2 {
pub use xyz::top_level_2::*;
}
}
);
}
}