use super::resolver::{
PreprocessorGuard, VisibilityIndex, merge_preprocessor_guards, preprocessor_guard_environment,
};
use crate::source_context::cpp_guard_set_to_runtime;
use brokk_bifrost_core::analyzer::ProjectFile;
use brokk_bifrost_core::hash::{HashMap, HashSet};
use std::sync::Arc;
use tree_sitter::Node;
#[derive(Debug)]
pub struct NamespaceAliasSite {
source: ProjectFile,
start: usize,
end: usize,
target: Option<Vec<String>>,
target_global: bool,
namespace: Vec<String>,
block: Option<(usize, usize)>,
guards: Option<HashSet<PreprocessorGuard>>,
}
impl NamespaceAliasSite {
fn same_target(&self, other: &Self) -> bool {
self.target == other.target
&& self.target_global == other.target_global
&& self.namespace == other.namespace
}
}
pub type NamespaceAliasCell =
Arc<super::resolver::VisibilityCell<Arc<HashMap<String, Vec<NamespaceAliasSite>>>>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NamespaceQualifierBinding {
Unaliased,
Bound {
components: Vec<String>,
global: bool,
lexical_scope: Vec<String>,
depth: usize,
},
Unresolved { depth: usize },
}
impl NamespaceQualifierBinding {
pub fn depth(&self) -> Option<usize> {
match self {
Self::Unaliased => None,
Self::Bound { depth, .. } | Self::Unresolved { depth } => Some(*depth),
}
}
}
const MAX_NAMESPACE_ALIAS_HOPS: usize = 32;
enum SiteState {
InEffect,
Possible,
Hidden,
}
struct AliasLookup<'r, 'tree> {
file: &'r ProjectFile,
reference: Node<'tree>,
sites: &'r HashMap<String, Vec<NamespaceAliasSite>>,
raw_reference_guards: Option<HashSet<PreprocessorGuard>>,
}
impl VisibilityIndex<'_> {
pub fn bind_namespace_qualifier(
&self,
file: &ProjectFile,
reference: Node<'_>,
components: &[String],
global: bool,
lexical_scope: &[String],
) -> NamespaceQualifierBinding {
if components.len() < 2 {
return NamespaceQualifierBinding::Unaliased;
}
let cell = self.namespace_alias_cell(file);
let sites = cell.get_or_init(|| Arc::new(self.collect_namespace_alias_sites(file)));
if !components[..components.len() - 1]
.iter()
.any(|component| sites.contains_key(component))
{
return NamespaceQualifierBinding::Unaliased;
}
let lookup = AliasLookup {
file,
reference,
sites: &sites,
raw_reference_guards: self
.cpp()
.prepared_syntax(self.token(), file)
.and_then(|prepared| preprocessor_guard_environment(reference, prepared.source())),
};
self.bind_qualifier(
&lookup,
components,
global,
lexical_scope,
(file, reference.start_byte()),
MAX_NAMESPACE_ALIAS_HOPS,
)
}
fn collect_namespace_alias_sites(
&self,
root: &ProjectFile,
) -> HashMap<String, Vec<NamespaceAliasSite>> {
let mut sites: HashMap<String, Vec<NamespaceAliasSite>> = HashMap::default();
for source in self.visible_source_files(root) {
let Some(facts) = self.cpp().declaration_source_facts(self.token(), &source) else {
continue;
};
for alias in &facts.facts.namespace_aliases {
let range = facts.source.occurrence(alias.occurrence).range;
sites
.entry(alias.name.clone())
.or_default()
.push(NamespaceAliasSite {
source: source.clone(),
start: range.start_byte,
end: range.end_byte,
target: alias.target.clone(),
target_global: alias.target_global,
namespace: alias.namespace.clone(),
block: alias.block.map(|block| {
let range = facts.source.occurrence(block).range;
(range.start_byte, range.end_byte)
}),
guards: alias.guards.as_ref().and_then(cpp_guard_set_to_runtime),
});
}
}
sites
}
fn bind_qualifier(
&self,
lookup: &AliasLookup<'_, '_>,
components: &[String],
global: bool,
lexical_scope: &[String],
position: (&ProjectFile, usize),
hops: usize,
) -> NamespaceQualifierBinding {
let qualifier = &components[..components.len() - 1];
let precedes =
|site: &NamespaceAliasSite| site.source != *position.0 || site.end <= position.1;
if !global {
let block_sites = lookup
.sites
.get(&qualifier[0])
.into_iter()
.flatten()
.filter(|site| {
site.block.is_some_and(|(start, end)| {
site.source == *position.0 && start <= position.1 && position.1 < end
}) && precedes(site)
&& lexical_scope.starts_with(&site.namespace)
});
if let Some(innermost) = block_sites.clone().filter_map(|site| site.block).max() {
let candidates = block_sites
.filter(|site| site.block == Some(innermost))
.collect::<Vec<_>>();
return self.bind_through(
lookup,
&candidates,
components,
0,
lexical_scope.len() + 1,
hops,
);
}
}
let innermost_tier = if global { 0 } else { lexical_scope.len() };
for tier in (0..=innermost_tier).rev() {
let mut owner = lexical_scope[..tier].to_vec();
for (index, name) in qualifier.iter().enumerate() {
let candidates = lookup
.sites
.get(name)
.into_iter()
.flatten()
.filter(|site| {
site.block.is_none() && site.namespace == owner && precedes(site)
})
.collect::<Vec<_>>();
if !candidates.is_empty() {
match self.bind_through(lookup, &candidates, components, index, tier, hops) {
NamespaceQualifierBinding::Unaliased => {}
binding => return binding,
}
}
owner.push(name.clone());
}
}
NamespaceQualifierBinding::Unaliased
}
fn bind_through(
&self,
lookup: &AliasLookup<'_, '_>,
candidates: &[&NamespaceAliasSite],
components: &[String],
index: usize,
depth: usize,
hops: usize,
) -> NamespaceQualifierBinding {
let mut bound: Option<&NamespaceAliasSite> = None;
let mut unresolved = false;
for site in candidates {
match self.namespace_alias_site_state(lookup, site) {
SiteState::Hidden => {}
SiteState::Possible => unresolved = true,
SiteState::InEffect => match bound {
None => bound = Some(site),
Some(previous) if previous.same_target(site) => {}
Some(_) => unresolved = true,
},
}
}
if unresolved {
return NamespaceQualifierBinding::Unresolved { depth };
}
let Some(site) = bound else {
return NamespaceQualifierBinding::Unaliased;
};
let (Some(target), Some(hops)) = (&site.target, hops.checked_sub(1)) else {
return NamespaceQualifierBinding::Unresolved { depth };
};
let mut rebound = target.clone();
rebound.extend_from_slice(&components[index + 1..]);
match self.bind_qualifier(
lookup,
&rebound,
site.target_global,
&site.namespace,
(&site.source, site.start),
hops,
) {
NamespaceQualifierBinding::Unaliased => NamespaceQualifierBinding::Bound {
components: rebound,
global: site.target_global,
lexical_scope: site.namespace.clone(),
depth,
},
NamespaceQualifierBinding::Bound {
components,
global,
lexical_scope,
..
} => NamespaceQualifierBinding::Bound {
components,
global,
lexical_scope,
depth,
},
NamespaceQualifierBinding::Unresolved { .. } => {
NamespaceQualifierBinding::Unresolved { depth }
}
}
}
fn namespace_alias_site_state(
&self,
lookup: &AliasLookup<'_, '_>,
site: &NamespaceAliasSite,
) -> SiteState {
let Some(guards) = &site.guards else {
return SiteState::Possible;
};
if self.binding_site_visible_at_reference(
lookup.file,
&site.source,
site.end,
guards,
lookup.reference,
) {
return SiteState::InEffect;
}
let reachable_in_order =
site.source != *lookup.file || site.end <= lookup.reference.start_byte();
if reachable_in_order
&& !guards.is_empty()
&& lookup
.raw_reference_guards
.as_ref()
.is_some_and(|active| merge_preprocessor_guards(guards, active).is_some())
{
SiteState::Possible
} else {
SiteState::Hidden
}
}
}