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