MCPcopy Create free account
hub / github.com/PHPantom-dev/phpantom_lsp / toposort_classes

Function toposort_classes

src/toposort.rs:111–196  ·  view source on GitHub ↗

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)>,
)

Source from the content-addressed store, hash-verified

109/// `populator::sorter::sort_class_likes`, but uses O(1) stack space
110/// per class regardless of hierarchy depth.
111pub(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 }

Callers 15

linear_chainFunction · 0.85
trait_dependencyFunction · 0.85
interface_dependencyFunction · 0.85
mixin_dependencyFunction · 0.85
cycle_does_not_panicFunction · 0.85
diamond_inheritanceFunction · 0.85
empty_inputFunction · 0.85
single_class_no_depsFunction · 0.85

Calls 8

class_dependenciesFunction · 0.85
pushMethod · 0.80
cloneMethod · 0.80
containsMethod · 0.80
removeMethod · 0.80
insertMethod · 0.45
getMethod · 0.45
unwrapMethod · 0.45

Tested by 13

linear_chainFunction · 0.68
trait_dependencyFunction · 0.68
interface_dependencyFunction · 0.68
mixin_dependencyFunction · 0.68
cycle_does_not_panicFunction · 0.68
diamond_inheritanceFunction · 0.68
empty_inputFunction · 0.68
single_class_no_depsFunction · 0.68
self_referencing_classFunction · 0.68