1use super::resolver::{
26 PreprocessorGuard, VisibilityIndex, merge_preprocessor_guards, preprocessor_guard_environment,
27};
28use crate::source_context::cpp_guard_set_to_runtime;
29use brokk_bifrost_core::analyzer::ProjectFile;
30use brokk_bifrost_core::hash::{HashMap, HashSet};
31use std::sync::{Arc, OnceLock};
32use tree_sitter::Node;
33
34#[derive(Debug)]
36pub struct NamespaceAliasSite {
37 source: ProjectFile,
38 start: usize,
39 end: usize,
40 target: Option<Vec<String>>,
41 target_global: bool,
42 namespace: Vec<String>,
43 block: Option<(usize, usize)>,
44 guards: Option<HashSet<PreprocessorGuard>>,
45}
46
47impl NamespaceAliasSite {
48 fn same_target(&self, other: &Self) -> bool {
49 self.target == other.target
50 && self.target_global == other.target_global
51 && self.namespace == other.namespace
52 }
53}
54
55pub type NamespaceAliasCell = Arc<OnceLock<HashMap<String, Vec<NamespaceAliasSite>>>>;
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum NamespaceQualifierBinding {
61 Unaliased,
63 Bound {
67 components: Vec<String>,
68 global: bool,
69 lexical_scope: Vec<String>,
70 depth: usize,
71 },
72 Unresolved { depth: usize },
74}
75
76impl NamespaceQualifierBinding {
77 pub fn depth(&self) -> Option<usize> {
81 match self {
82 Self::Unaliased => None,
83 Self::Bound { depth, .. } | Self::Unresolved { depth } => Some(*depth),
84 }
85 }
86}
87
88const MAX_NAMESPACE_ALIAS_HOPS: usize = 32;
91
92enum SiteState {
93 InEffect,
94 Possible,
95 Hidden,
96}
97
98struct AliasLookup<'r, 'tree> {
99 file: &'r ProjectFile,
100 reference: Node<'tree>,
101 sites: &'r HashMap<String, Vec<NamespaceAliasSite>>,
102 raw_reference_guards: Option<HashSet<PreprocessorGuard>>,
103}
104
105impl VisibilityIndex<'_> {
106 pub fn bind_namespace_qualifier(
110 &self,
111 file: &ProjectFile,
112 reference: Node<'_>,
113 components: &[String],
114 global: bool,
115 lexical_scope: &[String],
116 ) -> NamespaceQualifierBinding {
117 if components.len() < 2 {
118 return NamespaceQualifierBinding::Unaliased;
119 }
120 let cell = self.namespace_alias_cell(file);
121 let sites = cell.get_or_init(|| self.collect_namespace_alias_sites(file));
122 if !components[..components.len() - 1]
123 .iter()
124 .any(|component| sites.contains_key(component))
125 {
126 return NamespaceQualifierBinding::Unaliased;
127 }
128 let lookup = AliasLookup {
129 file,
130 reference,
131 sites,
132 raw_reference_guards: self
133 .cpp()
134 .prepared_syntax(self.token(), file)
135 .and_then(|prepared| preprocessor_guard_environment(reference, prepared.source())),
136 };
137 self.bind_qualifier(
138 &lookup,
139 components,
140 global,
141 lexical_scope,
142 (file, reference.start_byte()),
143 MAX_NAMESPACE_ALIAS_HOPS,
144 )
145 }
146
147 fn collect_namespace_alias_sites(
148 &self,
149 root: &ProjectFile,
150 ) -> HashMap<String, Vec<NamespaceAliasSite>> {
151 let mut sites: HashMap<String, Vec<NamespaceAliasSite>> = HashMap::default();
152 for source in self.visible_source_files(root) {
153 let Some(facts) = self.cpp().declaration_source_facts(self.token(), &source) else {
154 continue;
155 };
156 for alias in &facts.facts.namespace_aliases {
157 let range = facts.source.occurrence(alias.occurrence).range;
158 sites
159 .entry(alias.name.clone())
160 .or_default()
161 .push(NamespaceAliasSite {
162 source: source.clone(),
163 start: range.start_byte,
164 end: range.end_byte,
165 target: alias.target.clone(),
166 target_global: alias.target_global,
167 namespace: alias.namespace.clone(),
168 block: alias.block.map(|block| {
169 let range = facts.source.occurrence(block).range;
170 (range.start_byte, range.end_byte)
171 }),
172 guards: alias.guards.as_ref().and_then(cpp_guard_set_to_runtime),
173 });
174 }
175 }
176 sites
177 }
178
179 fn bind_qualifier(
183 &self,
184 lookup: &AliasLookup<'_, '_>,
185 components: &[String],
186 global: bool,
187 lexical_scope: &[String],
188 position: (&ProjectFile, usize),
189 hops: usize,
190 ) -> NamespaceQualifierBinding {
191 let qualifier = &components[..components.len() - 1];
192 let precedes =
193 |site: &NamespaceAliasSite| site.source != *position.0 || site.end <= position.1;
194 if !global {
195 let block_sites = lookup
197 .sites
198 .get(&qualifier[0])
199 .into_iter()
200 .flatten()
201 .filter(|site| {
202 site.block.is_some_and(|(start, end)| {
203 site.source == *position.0 && start <= position.1 && position.1 < end
204 }) && precedes(site)
205 && lexical_scope.starts_with(&site.namespace)
206 });
207 if let Some(innermost) = block_sites.clone().filter_map(|site| site.block).max() {
208 let candidates = block_sites
209 .filter(|site| site.block == Some(innermost))
210 .collect::<Vec<_>>();
211 return self.bind_through(
212 lookup,
213 &candidates,
214 components,
215 0,
216 lexical_scope.len() + 1,
217 hops,
218 );
219 }
220 }
221 let innermost_tier = if global { 0 } else { lexical_scope.len() };
222 for tier in (0..=innermost_tier).rev() {
223 let mut owner = lexical_scope[..tier].to_vec();
224 for (index, name) in qualifier.iter().enumerate() {
225 let candidates = lookup
226 .sites
227 .get(name)
228 .into_iter()
229 .flatten()
230 .filter(|site| {
231 site.block.is_none() && site.namespace == owner && precedes(site)
232 })
233 .collect::<Vec<_>>();
234 if !candidates.is_empty() {
235 match self.bind_through(lookup, &candidates, components, index, tier, hops) {
236 NamespaceQualifierBinding::Unaliased => {}
237 binding => return binding,
238 }
239 }
240 owner.push(name.clone());
241 }
242 }
243 NamespaceQualifierBinding::Unaliased
244 }
245
246 fn bind_through(
250 &self,
251 lookup: &AliasLookup<'_, '_>,
252 candidates: &[&NamespaceAliasSite],
253 components: &[String],
254 index: usize,
255 depth: usize,
256 hops: usize,
257 ) -> NamespaceQualifierBinding {
258 let mut bound: Option<&NamespaceAliasSite> = None;
259 let mut unresolved = false;
260 for site in candidates {
261 match self.namespace_alias_site_state(lookup, site) {
262 SiteState::Hidden => {}
263 SiteState::Possible => unresolved = true,
264 SiteState::InEffect => match bound {
265 None => bound = Some(site),
266 Some(previous) if previous.same_target(site) => {}
267 Some(_) => unresolved = true,
268 },
269 }
270 }
271 if unresolved {
272 return NamespaceQualifierBinding::Unresolved { depth };
273 }
274 let Some(site) = bound else {
275 return NamespaceQualifierBinding::Unaliased;
276 };
277 let (Some(target), Some(hops)) = (&site.target, hops.checked_sub(1)) else {
278 return NamespaceQualifierBinding::Unresolved { depth };
279 };
280 let mut rebound = target.clone();
281 rebound.extend_from_slice(&components[index + 1..]);
282 match self.bind_qualifier(
283 lookup,
284 &rebound,
285 site.target_global,
286 &site.namespace,
287 (&site.source, site.start),
288 hops,
289 ) {
290 NamespaceQualifierBinding::Unaliased => NamespaceQualifierBinding::Bound {
291 components: rebound,
292 global: site.target_global,
293 lexical_scope: site.namespace.clone(),
294 depth,
295 },
296 NamespaceQualifierBinding::Bound {
297 components,
298 global,
299 lexical_scope,
300 ..
301 } => NamespaceQualifierBinding::Bound {
302 components,
303 global,
304 lexical_scope,
305 depth,
306 },
307 NamespaceQualifierBinding::Unresolved { .. } => {
308 NamespaceQualifierBinding::Unresolved { depth }
309 }
310 }
311 }
312
313 fn namespace_alias_site_state(
314 &self,
315 lookup: &AliasLookup<'_, '_>,
316 site: &NamespaceAliasSite,
317 ) -> SiteState {
318 let Some(guards) = &site.guards else {
319 return SiteState::Possible;
320 };
321 if self.binding_site_visible_at_reference(
322 lookup.file,
323 &site.source,
324 site.end,
325 guards,
326 lookup.reference,
327 ) {
328 return SiteState::InEffect;
329 }
330 let reachable_in_order =
333 site.source != *lookup.file || site.end <= lookup.reference.start_byte();
334 if reachable_in_order
335 && !guards.is_empty()
336 && lookup
337 .raw_reference_guards
338 .as_ref()
339 .is_some_and(|active| merge_preprocessor_guards(guards, active).is_some())
340 {
341 SiteState::Possible
342 } else {
343 SiteState::Hidden
344 }
345 }
346}