brokk_bifrost_cpp/
hierarchy.rs1use crate::declarations::normalize_cpp_whitespace;
9use crate::graph_support::CppSource;
10use crate::imports::{include_paths, resolve_include_targets_with_index};
11use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile};
12use brokk_bifrost_core::hash::HashSet;
13use brokk_bifrost_core::path_utils::rel_path_string;
14use brokk_bifrost_core::profiling;
15
16pub fn build_cpp_visible_type_units(
28 cpp: &dyn CppSource,
29 file: &ProjectFile,
30 keep_going: &dyn Fn() -> bool,
31) -> Option<Vec<CodeUnit>> {
32 let _scope =
33 profiling::scope_with(|| format!("cpp.visible_types.build[{}]", rel_path_string(file)));
34 let include_targets = cpp.include_target_index();
35 let mut visited = HashSet::default();
36 let mut declarations = Vec::new();
37 let mut pending = vec![file.clone()];
38 visited.insert(file.clone());
39
40 while let Some(current) = pending.pop() {
41 if !keep_going() {
42 return None;
43 }
44 {
45 let _decls = profiling::scope("cpp.visible_types.decls");
46 declarations.extend(
47 cpp.declarations(¤t)
48 .into_iter()
49 .filter(|unit| unit.is_class() || cpp.is_type_alias(unit)),
50 );
51 }
52
53 let imports = {
54 let _imports = profiling::scope("cpp.visible_types.imports");
55 cpp.import_statements(¤t)
56 };
57 for include in include_paths(&imports) {
58 for target in resolve_include_targets_with_index(¤t, &include, include_targets) {
59 if visited.insert(target.clone()) {
60 pending.push(target);
61 }
62 }
63 }
64 }
65
66 declarations.sort();
67 declarations.dedup();
68 profiling::note_with(|| {
69 format!(
70 "cpp.visible_types.done[{}] visited={} declarations={}",
71 rel_path_string(file),
72 visited.len(),
73 declarations.len()
74 )
75 });
76 Some(declarations)
77}
78
79pub fn cpp_resolve_direct_ancestors(
86 cpp: &dyn CppSource,
87 code_unit: &CodeUnit,
88 keep_going: &dyn Fn() -> bool,
89) -> Option<Vec<CodeUnit>> {
90 if !code_unit.is_class() || cpp.is_type_alias(code_unit) {
91 return Some(Vec::new());
92 }
93
94 let visible = cpp.visible_type_units_while(code_unit.source(), keep_going)?;
95 let mut ancestors = Vec::new();
96 for raw in cpp.raw_supertypes_of(code_unit) {
97 if let Some(ancestor) = resolve_base_type(cpp, code_unit, &raw, &visible)
98 && !ancestors.iter().any(|existing| existing == &ancestor)
99 {
100 ancestors.push(ancestor);
101 }
102 }
103 Some(ancestors)
104}
105
106fn resolve_base_type(
107 cpp: &dyn CppSource,
108 code_unit: &CodeUnit,
109 raw: &str,
110 visible: &[CodeUnit],
111) -> Option<CodeUnit> {
112 let normalized = normalize_cpp_type_reference(raw)?;
113 let resolved = if normalized.name.contains("::") || normalized.global {
114 resolve_qualified_type(
115 code_unit.package_name(),
116 &normalized.name,
117 normalized.global,
118 visible,
119 )
120 } else {
121 resolve_unqualified_base(code_unit, &normalized.name, visible)
122 }?;
123 canonicalize_alias(cpp, resolved, visible, &mut HashSet::default())
124}
125
126fn resolve_unqualified_base<'a>(
127 code_unit: &CodeUnit,
128 name: &str,
129 visible: &'a [CodeUnit],
130) -> Option<&'a CodeUnit> {
131 for namespace in namespace_search_order(code_unit.package_name()) {
132 if let Some(candidate) = visible.iter().find(|candidate| {
133 candidate.identifier() == name && candidate.package_name() == namespace
134 }) {
135 return Some(candidate);
136 }
137 }
138
139 visible
140 .iter()
141 .find(|candidate| candidate.identifier() == name)
142}
143
144fn canonicalize_alias(
145 cpp: &dyn CppSource,
146 unit: &CodeUnit,
147 visible: &[CodeUnit],
148 seen: &mut HashSet<String>,
149) -> Option<CodeUnit> {
150 if !cpp.is_type_alias(unit) {
151 return Some(unit.clone());
152 }
153 if !seen.insert(unit.fq_name()) {
154 return None;
155 }
156 let target = alias_target_text(unit)?;
157 let resolved = if target.name.contains("::") || target.global {
158 resolve_qualified_type(unit.package_name(), &target.name, target.global, visible)
159 } else {
160 visible
161 .iter()
162 .find(|candidate| {
163 candidate.identifier() == target.name
164 && candidate.package_name() == unit.package_name()
165 })
166 .or_else(|| {
167 visible
168 .iter()
169 .find(|candidate| candidate.identifier() == target.name)
170 })
171 }?;
172 canonicalize_alias(cpp, resolved, visible, seen)
173}
174
175fn resolve_qualified_type<'a>(
176 lexical_namespace: &str,
177 name: &str,
178 global: bool,
179 visible: &'a [CodeUnit],
180) -> Option<&'a CodeUnit> {
181 let namespaces = if global {
182 vec![""]
183 } else {
184 namespace_search_order(lexical_namespace)
185 };
186 namespaces.into_iter().find_map(|namespace| {
187 let qualified = if namespace.is_empty() {
188 name.to_string()
189 } else {
190 format!("{namespace}::{name}")
191 };
192 visible
193 .iter()
194 .find(|candidate| cpp_name_for(candidate) == qualified)
195 })
196}
197
198fn namespace_search_order(package_name: &str) -> Vec<&str> {
199 let mut namespaces = Vec::new();
200 let mut current = package_name;
201 loop {
202 namespaces.push(current);
203 let Some((parent, _)) = current.rsplit_once("::") else {
204 if !current.is_empty() {
205 namespaces.push("");
206 }
207 return namespaces;
208 };
209 current = parent;
210 }
211}
212
213fn alias_target_text(alias: &CodeUnit) -> Option<NormalizedCppTypeReference> {
214 let signature = alias.signature()?.trim();
215 let target = signature
216 .strip_prefix("using ")
217 .and_then(|rest| rest.split_once('=').map(|(_, rhs)| rhs))
218 .or_else(|| {
219 signature
220 .strip_prefix("typedef ")
221 .and_then(|rest| rest.rsplit_once(' ').map(|(lhs, _)| lhs))
222 })?
223 .trim()
224 .trim_end_matches(';');
225 normalize_cpp_type_reference(target)
226}
227
228struct NormalizedCppTypeReference {
229 name: String,
230 global: bool,
231}
232
233fn normalize_cpp_type_reference(value: &str) -> Option<NormalizedCppTypeReference> {
234 let mut text = normalize_cpp_whitespace(value)
235 .trim_start_matches("new ")
236 .trim()
237 .to_string();
238 if let Some(index) = text.find(['(', '{']) {
239 text.truncate(index);
240 }
241 if let Some(index) = text.find('<') {
242 text.truncate(index);
243 }
244 let normalized = text
245 .trim()
246 .trim_start_matches("const ")
247 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
248 .trim();
249 let global = normalized.starts_with("::");
250 let normalized = normalized.trim_matches(':').trim();
251 let normalized = normalized
252 .strip_prefix("struct ")
253 .or_else(|| normalized.strip_prefix("class "))
254 .or_else(|| normalized.strip_prefix("enum "))
255 .unwrap_or(normalized)
256 .trim();
257 (!normalized.is_empty()).then(|| NormalizedCppTypeReference {
258 name: normalized.to_string(),
259 global,
260 })
261}
262
263fn cpp_name_for(unit: &CodeUnit) -> String {
264 let short = unit.short_name().replace(['.', '$'], "::");
265 if unit.package_name().is_empty() {
266 short
267 } else {
268 format!("{}::{}", unit.package_name(), short)
269 }
270}