Topologically sort class-like declarations by their dependencies. # Arguments `classes` — iterator of `(FQN, &ClassInfo)` pairs covering every known class-like declaration (from `uri_classes_index`, stubs, etc.). # Returns A `Vec ` of FQNs in dependency order: every class appears after all of its dependencies. Classes involved in cycles appear in an unspecified but safe order (the cycl
(
classes: impl Iterator<Item = (String, &'a ClassInfo)>,
)
| 109 | /// `populator::sorter::sort_class_likes`, but uses O(1) stack space |
| 110 | /// per class regardless of hierarchy depth. |
| 111 | pub(crate) fn toposort_classes<'a>( |
| 112 | classes: impl Iterator<Item = (String, &'a ClassInfo)>, |
| 113 | ) -> Vec<String> { |
| 114 | // Build a map from FQN → dependency list. |
| 115 | let mut dep_map: HashMap<String, Vec<String>> = HashMap::new(); |
| 116 | let mut all_fqns: Vec<String> = Vec::new(); |
| 117 | |
| 118 | for (fqn, class) in classes { |
| 119 | let deps = class_dependencies(class); |
| 120 | all_fqns.push(fqn.clone()); |
| 121 | dep_map.insert(fqn, deps); |
| 122 | } |
| 123 | |
| 124 | // Sort the starting FQNs so that the DFS visitation order is |
| 125 | // deterministic regardless of HashMap iteration order in the |
| 126 | // caller (e.g. `toposort_from_uri_classes_index` iterates a HashMap whose |
| 127 | // order varies between runs due to random hashing seeds). |
| 128 | // Without this, classes at the same topological level can be |
| 129 | // processed in different orders across runs, which causes the |
| 130 | // recursion guard in `resolve_class_fully_inner` to break |
| 131 | // implicit cycles differently — leading to non-deterministic |
| 132 | // cache contents and flaky diagnostics (see B28). |
| 133 | all_fqns.sort(); |
| 134 | |
| 135 | let mut visited: HashSet<String> = HashSet::with_capacity(all_fqns.len()); |
| 136 | let mut visiting: HashSet<String> = HashSet::new(); |
| 137 | let mut sorted: Vec<String> = Vec::with_capacity(all_fqns.len()); |
| 138 | |
| 139 | for start_fqn in &all_fqns { |
| 140 | if visited.contains(start_fqn) { |
| 141 | continue; |
| 142 | } |
| 143 | |
| 144 | // Iterative DFS starting from `start_fqn`. |
| 145 | let start_deps = dep_map.get(start_fqn).cloned().unwrap_or_default(); |
| 146 | |
| 147 | visiting.insert(start_fqn.clone()); |
| 148 | |
| 149 | let mut stack = vec![DfsFrame { |
| 150 | fqn: start_fqn.clone(), |
| 151 | dep_index: 0, |
| 152 | deps: start_deps, |
| 153 | }]; |
| 154 | |
| 155 | while let Some(frame) = stack.last_mut() { |
| 156 | if frame.dep_index < frame.deps.len() { |
| 157 | let dep = frame.deps[frame.dep_index].clone(); |
| 158 | frame.dep_index += 1; |
| 159 | |
| 160 | // Skip dependencies not in our input set (external/unloaded classes). |
| 161 | if !dep_map.contains_key(&dep) { |
| 162 | continue; |
| 163 | } |
| 164 | |
| 165 | // Already fully processed — nothing to do. |
| 166 | if visited.contains(&dep) { |
| 167 | continue; |
| 168 | } |