projectatlas_symbols/
declaration_identity.rs1use projectatlas_core::graph::{
4 GraphContractError, GraphIdentityText, MAX_GRAPH_IDENTITY_BYTES, QUALIFIED_SYMBOL_SCOPE_PREFIX,
5};
6use projectatlas_core::symbols::{SymbolGraph, SymbolKind};
7use std::collections::BTreeMap;
8
9pub fn qualified_symbol_parents(
19 graph: &SymbolGraph,
20) -> Result<Vec<Option<GraphIdentityText>>, GraphContractError> {
21 qualified_symbol_parents_checked(graph, &mut |_| Ok(()))
22}
23
24pub fn qualified_symbol_parents_checked<E: From<GraphContractError>>(
30 graph: &SymbolGraph,
31 reserve: &mut impl FnMut(u64) -> Result<(), E>,
32) -> Result<Vec<Option<GraphIdentityText>>, E> {
33 reserve((graph.symbols.len() as u64).saturating_mul(
34 (2 * std::mem::size_of::<Option<GraphIdentityText>>() + std::mem::size_of::<usize>())
35 as u64,
36 ))?;
37 qualified_symbol_parents_selected_checked(graph, (0..graph.symbols.len()).collect(), reserve)
38}
39
40pub(crate) fn qualified_symbol_parents_selected(
42 graph: &SymbolGraph,
43 order: Vec<usize>,
44) -> Result<Vec<Option<GraphIdentityText>>, GraphContractError> {
45 qualified_symbol_parents_selected_checked(graph, order, &mut |_| Ok(()))
46}
47
48fn qualified_symbol_parents_selected_checked<E: From<GraphContractError>>(
50 graph: &SymbolGraph,
51 mut order: Vec<usize>,
52 reserve: &mut impl FnMut(u64) -> Result<(), E>,
53) -> Result<Vec<Option<GraphIdentityText>>, E> {
54 let is_php = graph
55 .language
56 .as_deref()
57 .is_some_and(|language| language.eq_ignore_ascii_case("php"));
58 order.sort_by_key(|&index| (graph.symbols[index].line_start, index));
59 let mut active_by_name = BTreeMap::<&str, Vec<usize>>::new();
60 let mut qualified_names = vec![None::<GraphIdentityText>; graph.symbols.len()];
61 let mut parents = vec![None; graph.symbols.len()];
62 for index in order {
63 let symbol = &graph.symbols[index];
64 let name = source_symbol_identity(symbol.name.clone())?;
65 let mut parent = symbol
66 .parent
67 .clone()
68 .map(source_symbol_identity)
69 .transpose()?;
70 if let Some(immediate_parent) = parent.as_ref()
71 && let Some(candidates) = active_by_name.get_mut(immediate_parent.as_str())
72 {
73 while candidates.last().is_some_and(|&candidate_index| {
74 let candidate = &graph.symbols[candidate_index];
75 if is_php
78 && candidate.kind != SymbolKind::Module
79 && let Some((outer, inner)) =
80 candidate.source_selector.zip(symbol.source_selector)
81 {
82 outer.byte_end < inner.byte_end
83 } else {
84 candidate.line_end < symbol.line_end
85 }
86 }) {
87 candidates.pop();
88 }
89 if let Some(qualified_parent) = candidates
90 .last()
91 .and_then(|&candidate_index| qualified_names[candidate_index].clone())
92 {
93 parent = Some(qualified_parent);
94 }
95 }
96 let qualified = match parent.as_ref() {
97 Some(parent) => qualified_symbol_identity(parent, &name)?,
98 None => name,
99 };
100 reserve(
101 128_u64
102 .saturating_add((4 * std::mem::size_of::<usize>()) as u64)
103 .saturating_add((qualified.as_str().len() as u64).saturating_mul(2))
104 .saturating_add(
105 parent
106 .as_ref()
107 .map_or(0, |parent| parent.as_str().len() as u64)
108 .saturating_mul(2),
109 ),
110 )?;
111 qualified_names[index] = Some(qualified);
112 parents[index] = parent;
113 active_by_name
114 .entry(symbol.name.as_str())
115 .or_default()
116 .push(index);
117 }
118 Ok(parents)
119}
120
121pub fn validate_source_symbol_identity(value: &str) -> Result<(), GraphContractError> {
126 GraphIdentityText::validate(value)?;
127 validate_source_scope_namespace(value)
128}
129
130fn validate_source_scope_namespace(value: &str) -> Result<(), GraphContractError> {
132 if value.starts_with(QUALIFIED_SYMBOL_SCOPE_PREFIX) {
133 return Err(GraphContractError::InvalidIdentityText {
134 reason: "source symbol identity uses the reserved derived-scope namespace",
135 });
136 }
137 Ok(())
138}
139
140pub fn source_symbol_identity(value: String) -> Result<GraphIdentityText, GraphContractError> {
145 let identity = GraphIdentityText::new(value)?;
146 validate_source_scope_namespace(identity.as_str())?;
147 Ok(identity)
148}
149
150pub fn qualified_symbol_identity(
155 parent: &GraphIdentityText,
156 name: &GraphIdentityText,
157) -> Result<GraphIdentityText, GraphContractError> {
158 const SEPARATOR: &str = "::";
159 const DIGEST_DOMAIN: &str = "projectatlas.graph.qualified-symbol-scope.v1";
160 let qualified_len = parent.as_str().len() + SEPARATOR.len() + name.as_str().len();
161 if qualified_len <= MAX_GRAPH_IDENTITY_BYTES {
162 let mut qualified = String::with_capacity(qualified_len);
163 qualified.push_str(parent.as_str());
164 qualified.push_str(SEPARATOR);
165 qualified.push_str(name.as_str());
166 return GraphIdentityText::new(qualified);
167 }
168
169 let compact_len =
170 QUALIFIED_SYMBOL_SCOPE_PREFIX.len() + 64 + SEPARATOR.len() + name.as_str().len();
171 if compact_len > MAX_GRAPH_IDENTITY_BYTES {
172 return Err(GraphContractError::InvalidIdentityText {
173 reason: "derived scope cannot retain its nearest admitted symbol name",
174 });
175 }
176 let mut hasher = blake3::Hasher::new_derive_key(DIGEST_DOMAIN);
177 hasher.update(parent.as_str().as_bytes());
178 hasher.update(SEPARATOR.as_bytes());
179 hasher.update(name.as_str().as_bytes());
180 let digest = hasher.finalize().to_hex();
181 let mut compact = String::with_capacity(compact_len);
182 compact.push_str(QUALIFIED_SYMBOL_SCOPE_PREFIX);
183 compact.push_str(digest.as_str());
184 compact.push_str(SEPARATOR);
185 compact.push_str(name.as_str());
186 GraphIdentityText::new(compact)
187}