Skip to main content

code_moniker_core/lang/sdk/
model.rs

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}