serde_generate/analyzer.rs
1// Copyright (c) Facebook, Inc. and its affiliates
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use serde_reflection::{ContainerFormat, Format, FormatHolder, Registry, Result};
5use std::collections::{BTreeMap, BTreeSet, HashSet};
6
7/// Compute dependencies while ignoring external names.
8fn get_dependencies<'a>(
9    format: &'a ContainerFormat,
10    external: &BTreeSet<String>,
11) -> Result<BTreeSet<&'a str>> {
12    let mut result = BTreeSet::new();
13    format.visit(&mut |format| {
14        if let Format::TypeName(x) = format {
15            if !external.contains(x) {
16                result.insert(x.as_str());
17            }
18        }
19        Ok(())
20    })?;
21    Ok(result)
22}
23
24/// Build a map of dependencies between the entries of a `Registry`.
25/// * By definition, an entry named `x` depends on `y` iff the container format of `x` in the registry
26///   syntactically contains a reference to `y` (i.e. an expression `Format::TypeName(y)`).
27/// * Dependencies can play a role in code generation in some languages (e.g. Rust or C++) where inductive
28///   definitions may require explicit "boxing" (i.e. adding pointer indirections) to ensure finite object sizes.
29pub fn get_dependency_map(registry: &Registry) -> Result<BTreeMap<&str, BTreeSet<&str>>> {
30    get_dependency_map_with_external_dependencies(registry, &BTreeSet::new())
31}
32
33/// Same as get_dependency_map but allow to specify a set of externally-provided names to ignore.
34pub fn get_dependency_map_with_external_dependencies<'a>(
35    registry: &'a Registry,
36    external: &BTreeSet<String>,
37) -> Result<BTreeMap<&'a str, BTreeSet<&'a str>>> {
38    let mut children = BTreeMap::new();
39    for (name, format) in registry {
40        children.insert(name.as_str(), get_dependencies(format, external)?);
41    }
42    Ok(children)
43}
44
45/// Classic topological sorting algorithm except that it doesn't abort in case of cycles.
46pub fn best_effort_topological_sort<T>(children: &BTreeMap<T, BTreeSet<T>>) -> Vec<T>
47where
48    T: Clone + std::cmp::Ord + std::cmp::Eq + std::hash::Hash,
49{
50    // Build the initial queue so that we pick up nodes with less children first (and otherwise
51    // those with smaller key first).
52    // This is a heuristic to break cycles preferably at large nodes (see comment below).
53    let mut queue: Vec<_> = children.keys().rev().cloned().collect();
54    queue.sort_by(|node1, node2| children[node1].len().cmp(&children[node2].len()));
55
56    let mut result = Vec::new();
57    // Nodes already inserted in result.
58    let mut sorted = HashSet::new();
59    // Nodes for which children have been enqueued.
60    let mut seen = HashSet::new();
61
62    while let Some(node) = queue.pop() {
63        if sorted.contains(&node) {
64            continue;
65        }
66        if seen.contains(&node) {
67            // Second time we see this node.
68            // * If `node` does not belong to a cycle in the graph, then by now, all its children
69            // have been sorted.
70            // * If `node` has children that depend back on it. We may be visiting `node` again
71            // before some of those children. Just insert `node` here. By ignoring edges going back
72            // to `node` now, we are effectively deciding to "break the cycle" there in future
73            // applications (e.g. `node` may be forward-declared in C++ and `Box`-ed in Rust).
74            sorted.insert(node.clone());
75            result.push(node);
76            continue;
77        }
78        // First time we see this node:
79        // 1. Mark it so that it is no longer enqueued.
80        seen.insert(node.clone());
81        // 2. Schedule all the (yet unseen) children then this node for a second visit.
82        // (If possible, visit children by increasing key.)
83        queue.push(node.clone());
84        for child in children[&node].iter().rev() {
85            if !seen.contains(child) {
86                queue.push(child.clone());
87            }
88        }
89    }
90    result
91}