1use std::collections::BTreeMap;
2use std::hash::{Hash, Hasher};
3
4use crate::core::code_graph::Position;
5use crate::core::moniker::Moniker;
6use rustc_hash::FxHashMap;
7
8use super::scope::{Namespace, ScopeId, ScopeTree};
9
10#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
11pub struct DefNameKey {
12 pub namespace: Namespace,
13 pub name: Vec<u8>,
14}
15
16impl DefNameKey {
17 pub fn new(namespace: Namespace, name: impl Into<Vec<u8>>) -> Self {
18 Self {
19 namespace,
20 name: name.into(),
21 }
22 }
23}
24
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct DiscoveredDef {
27 pub moniker: Moniker,
28 pub parent: Moniker,
29 pub namespace: Namespace,
30 pub name: Vec<u8>,
31 pub kind: &'static [u8],
32 pub visibility: &'static [u8],
33 pub signature: Vec<u8>,
34 pub position: Option<Position>,
35 pub call_name: Vec<u8>,
36 pub call_arity: Option<usize>,
37}
38
39impl DiscoveredDef {
40 pub fn key(&self) -> DefNameKey {
41 DefNameKey::new(self.namespace, self.name.clone())
42 }
43}
44
45#[derive(Clone, Debug, Default, Eq, PartialEq)]
46pub struct DefIndex {
47 by_moniker: BTreeMap<Moniker, DiscoveredDef>,
48 by_name: BTreeMap<DefNameKey, Vec<Moniker>>,
49}
50
51impl DefIndex {
52 pub fn from_defs(defs: &[DiscoveredDef]) -> Self {
53 let mut index = Self::default();
54 for def in defs {
55 index.insert(def.clone());
56 }
57 index
58 }
59
60 pub fn insert(&mut self, def: DiscoveredDef) {
61 self.by_name
62 .entry(def.key())
63 .or_default()
64 .push(def.moniker.clone());
65 self.by_moniker.insert(def.moniker.clone(), def);
66 }
67
68 pub fn contains(&self, moniker: &Moniker) -> bool {
69 self.by_moniker.contains_key(moniker)
70 }
71
72 pub fn get(&self, moniker: &Moniker) -> Option<&DiscoveredDef> {
73 self.by_moniker.get(moniker)
74 }
75
76 pub fn by_name(&self, namespace: Namespace, name: &[u8]) -> &[Moniker] {
77 self.by_name
78 .get(&DefNameKey::new(namespace, name.to_vec()))
79 .map(Vec::as_slice)
80 .unwrap_or_default()
81 }
82}
83
84#[derive(Clone, Debug, Eq, PartialEq)]
85pub enum ImportKind {
86 Symbol,
87 Module,
88 Wildcard,
89 Alias,
90 Reexport,
91}
92
93#[derive(Clone, Debug, Eq, PartialEq)]
94pub struct ImportTarget {
95 pub kind: ImportKind,
96 pub namespace: Namespace,
97 pub alias: Vec<u8>,
98 pub target: Moniker,
99 pub confidence: &'static [u8],
100}
101
102#[derive(Clone, Debug, Default, Eq, PartialEq)]
103pub struct ImportTable {
104 by_scope: BTreeMap<ScopeId, Vec<ImportTarget>>,
105}
106
107impl ImportTable {
108 pub fn insert(&mut self, scope: ScopeId, target: ImportTarget) {
109 self.by_scope.entry(scope).or_default().push(target);
110 }
111
112 pub fn scoped(&self, scope: ScopeId) -> &[ImportTarget] {
113 self.by_scope
114 .get(&scope)
115 .map(Vec::as_slice)
116 .unwrap_or_default()
117 }
118}
119
120#[derive(Clone, Debug, Eq, PartialEq)]
121pub struct DiscoveredFile {
122 pub root: Moniker,
123 pub root_kind: &'static [u8],
124 pub defs: Vec<DiscoveredDef>,
125 pub def_index: DefIndex,
126 pub scopes: ScopeTree,
127 pub imports: ImportTable,
128}
129
130impl DiscoveredFile {
131 pub fn new(
132 root: Moniker,
133 root_kind: &'static [u8],
134 defs: Vec<DiscoveredDef>,
135 scopes: ScopeTree,
136 imports: ImportTable,
137 ) -> Self {
138 let def_index = DefIndex::from_defs(&defs);
139 Self {
140 root,
141 root_kind,
142 defs,
143 def_index,
144 scopes,
145 imports,
146 }
147 }
148}
149
150#[derive(Clone, Debug, Eq, PartialEq)]
151pub enum TargetExpr {
152 Bare(Vec<u8>),
153 Path(Vec<Vec<u8>>),
154 Receiver {
155 receiver: Box<TargetExpr>,
156 name: Vec<u8>,
157 },
158 SelfType(Vec<u8>),
159 External {
160 package: Vec<u8>,
161 path: Vec<Vec<u8>>,
162 },
163}
164
165#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
166pub struct RefHints {
167 pub receiver_hint: Vec<u8>,
168 pub alias: Vec<u8>,
169 pub namespace: Option<Namespace>,
170 pub call_name: Vec<u8>,
171 pub call_arity: Option<usize>,
172}
173
174#[derive(Clone, Debug, Eq, PartialEq)]
175pub struct UnresolvedRef {
176 pub source: Moniker,
177 pub kind: &'static [u8],
178 pub source_scope: ScopeId,
179 pub position: Option<Position>,
180 pub target: TargetExpr,
181 pub hints: RefHints,
182}
183
184#[derive(Clone, Debug, Eq, PartialEq)]
185pub struct ResolvedRef {
186 pub source: Moniker,
187 pub target: Moniker,
188 pub kind: &'static [u8],
189 pub position: Option<Position>,
190 pub confidence: &'static [u8],
191 pub hints: RefHints,
192}
193
194#[derive(Clone, Debug, Default)]
195pub(crate) struct ResolvedRefDeduper {
196 unique: FxHashMap<u64, usize>,
197 collisions: FxHashMap<u64, Vec<usize>>,
198 include_hints: bool,
199}
200
201impl ResolvedRefDeduper {
202 pub(crate) fn with_hints() -> Self {
203 Self {
204 include_hints: true,
205 ..Self::default()
206 }
207 }
208
209 pub(crate) fn push(&mut self, refs: &mut Vec<ResolvedRef>, reference: ResolvedRef) {
210 let hash = resolved_ref_hash(&reference, self.include_hints);
211 self.push_hashed(refs, reference, hash);
212 }
213
214 fn push_hashed(&mut self, refs: &mut Vec<ResolvedRef>, reference: ResolvedRef, hash: u64) {
215 let candidates = self
216 .collisions
217 .get(&hash)
218 .map(Vec::as_slice)
219 .or_else(|| self.unique.get(&hash).map(std::slice::from_ref))
220 .unwrap_or_default();
221 if candidates
222 .iter()
223 .any(|index| same_ref(&refs[*index], &reference, self.include_hints))
224 {
225 return;
226 }
227
228 let index = refs.len();
229 refs.push(reference);
230 let Some(existing) = self.unique.get(&hash).copied() else {
231 self.unique.insert(hash, index);
232 return;
233 };
234 self.collisions
235 .entry(hash)
236 .or_insert_with(|| vec![existing])
237 .push(index);
238 }
239}
240
241fn resolved_ref_hash(reference: &ResolvedRef, include_hints: bool) -> u64 {
242 let mut hasher = rustc_hash::FxHasher::default();
243 reference.source.hash(&mut hasher);
244 reference.target.hash(&mut hasher);
245 reference.kind.hash(&mut hasher);
246 reference.position.hash(&mut hasher);
247 reference.confidence.hash(&mut hasher);
248 if include_hints {
249 reference.hints.hash(&mut hasher);
250 }
251 hasher.finish()
252}
253
254fn same_ref(left: &ResolvedRef, right: &ResolvedRef, include_hints: bool) -> bool {
255 left.source == right.source
256 && left.target == right.target
257 && left.kind == right.kind
258 && left.position == right.position
259 && left.confidence == right.confidence
260 && (!include_hints || left.hints == right.hints)
261}
262
263#[cfg(test)]
264mod resolved_ref_deduper_tests {
265 use super::*;
266 use crate::core::moniker::MonikerBuilder;
267
268 fn reference(target: &[u8], receiver_hint: &[u8]) -> ResolvedRef {
269 let mut source = MonikerBuilder::new();
270 source.project(b"app").segment(b"fn", b"source");
271 let mut destination = MonikerBuilder::new();
272 destination.project(b"app").segment(b"fn", target);
273 ResolvedRef {
274 source: source.build(),
275 target: destination.build(),
276 kind: b"calls",
277 position: Some((10, 20)),
278 confidence: b"resolved",
279 hints: RefHints {
280 receiver_hint: receiver_hint.to_vec(),
281 ..RefHints::default()
282 },
283 }
284 }
285
286 #[test]
287 fn exact_duplicates_and_hint_policy_match_existing_language_contracts() {
288 let mut refs = Vec::new();
289 let mut linkage = ResolvedRefDeduper::default();
290 linkage.push(&mut refs, reference(b"target", b"left"));
291 linkage.push(&mut refs, reference(b"target", b"right"));
292 assert_eq!(refs.len(), 1, "C/Go/Java ignore hints while deduplicating");
293
294 let mut refs = Vec::new();
295 let mut full = ResolvedRefDeduper::with_hints();
296 full.push(&mut refs, reference(b"target", b"left"));
297 full.push(&mut refs, reference(b"target", b"right"));
298 full.push(&mut refs, reference(b"target", b"right"));
299 assert_eq!(refs.len(), 2, "Rust keeps distinct hints");
300 }
301
302 #[test]
303 fn hash_collisions_are_resolved_by_exact_comparison() {
304 let mut refs = Vec::new();
305 let mut deduper = ResolvedRefDeduper::default();
306 deduper.push_hashed(&mut refs, reference(b"first", b""), 7);
307 deduper.push_hashed(&mut refs, reference(b"second", b""), 7);
308 deduper.push_hashed(&mut refs, reference(b"first", b""), 7);
309 assert_eq!(refs.len(), 2);
310 }
311}