ryo_analysis/discovery/
result.rs1use crate::symbol::{FileSpan, SymbolId, SymbolPath, Uuid, Visibility};
4use crate::SymbolKind;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Default)]
10pub struct DiscoveryResult {
11 pub symbols: Vec<DiscoveredSymbol>,
13 pub relations: Option<RelationGraph>,
15 pub total_matches: usize,
17 pub truncated: bool,
19}
20
21impl DiscoveryResult {
22 pub fn new() -> Self {
24 Self::default()
25 }
26
27 pub fn add(&mut self, symbol: DiscoveredSymbol) {
29 self.symbols.push(symbol);
30 }
31
32 pub fn len(&self) -> usize {
34 self.symbols.len()
35 }
36
37 pub fn is_empty(&self) -> bool {
39 self.symbols.is_empty()
40 }
41
42 pub fn first(&self) -> Option<&DiscoveredSymbol> {
44 self.symbols.first()
45 }
46
47 pub fn iter(&self) -> impl Iterator<Item = &DiscoveredSymbol> {
49 self.symbols.iter()
50 }
51
52 pub fn paths(&self) -> impl Iterator<Item = &SymbolPath> {
54 self.symbols.iter().map(|s| &s.path)
55 }
56
57 pub fn ids(&self) -> impl Iterator<Item = SymbolId> + '_ {
59 self.symbols.iter().map(|s| s.id)
60 }
61}
62
63#[derive(Debug, Clone, Serialize)]
65pub struct DiscoveredSymbol {
66 pub id: SymbolId,
68 #[serde(skip_serializing_if = "Option::is_none")]
73 pub uuid: Option<Uuid>,
74 pub path: SymbolPath,
76 pub kind: SymbolKind,
78 pub span: Option<FileSpan>,
80 pub visibility: Option<Visibility>,
82 pub score: f32,
84 pub ref_count: usize,
86 pub impl_count: usize,
88}
89
90impl DiscoveredSymbol {
91 pub fn new(id: SymbolId, path: SymbolPath, kind: SymbolKind) -> Self {
93 Self {
94 id,
95 uuid: None,
96 path,
97 kind,
98 span: None,
99 visibility: None,
100 score: 1.0,
101 ref_count: 0,
102 impl_count: 0,
103 }
104 }
105
106 pub fn with_uuid(mut self, uuid: Uuid) -> Self {
108 self.uuid = Some(uuid);
109 self
110 }
111
112 pub fn with_span(mut self, span: FileSpan) -> Self {
114 self.span = Some(span);
115 self
116 }
117
118 pub fn with_visibility(mut self, visibility: Visibility) -> Self {
120 self.visibility = Some(visibility);
121 self
122 }
123
124 pub fn with_score(mut self, score: f32) -> Self {
126 self.score = score;
127 self
128 }
129
130 pub fn with_ref_count(mut self, ref_count: usize) -> Self {
132 self.ref_count = ref_count;
133 self
134 }
135
136 pub fn with_impl_count(mut self, impl_count: usize) -> Self {
138 self.impl_count = impl_count;
139 self
140 }
141
142 pub fn is_public(&self) -> bool {
144 self.visibility.as_ref().is_some_and(|v| v.is_public())
145 }
146}
147
148#[derive(Debug, Clone, Default)]
150pub struct RelationGraph {
151 relations: HashMap<SymbolId, Vec<Relation>>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct Relation {
158 pub target: SymbolId,
160 pub kind: RelationKind,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
173pub enum RelationKind {
174 Calls,
176 CalledBy,
178 TypeReferences,
180 TypeReferencedBy,
182 Implements,
184 ImplementedBy,
186 Contains,
188 ContainedBy,
190}
191
192impl RelationGraph {
193 pub fn new() -> Self {
195 Self::default()
196 }
197
198 pub fn add(&mut self, source: SymbolId, target: SymbolId, kind: RelationKind) {
200 self.relations
201 .entry(source)
202 .or_default()
203 .push(Relation { target, kind });
204 }
205
206 pub fn get(&self, id: SymbolId) -> &[Relation] {
208 self.relations.get(&id).map_or(&[], |v| v.as_slice())
209 }
210
211 pub fn get_by_kind(&self, id: SymbolId, kind: RelationKind) -> Vec<SymbolId> {
213 self.get(id)
214 .iter()
215 .filter(|r| r.kind == kind)
216 .map(|r| r.target)
217 .collect()
218 }
219
220 pub fn is_empty(&self) -> bool {
222 self.relations.is_empty()
223 }
224
225 pub fn len(&self) -> usize {
227 self.relations.len()
228 }
229
230 pub fn iter(&self) -> impl Iterator<Item = (SymbolId, &[Relation])> {
232 self.relations
233 .iter()
234 .map(|(id, rels)| (*id, rels.as_slice()))
235 }
236
237 pub fn sources(&self) -> impl Iterator<Item = SymbolId> + '_ {
239 self.relations.keys().copied()
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_discovery_result() {
249 let result = DiscoveryResult::new();
250 assert!(result.is_empty());
251
252 assert_eq!(result.len(), 0);
255 }
256
257 #[test]
258 fn test_relation_graph() {
259 use slotmap::SlotMap;
260
261 let mut slots: SlotMap<SymbolId, ()> = SlotMap::with_key();
262 let id1 = slots.insert(());
263 let id2 = slots.insert(());
264
265 let mut graph = RelationGraph::new();
266 graph.add(id1, id2, RelationKind::Calls);
267
268 assert!(!graph.is_empty());
269 assert_eq!(graph.get(id1).len(), 1);
270 assert_eq!(graph.get_by_kind(id1, RelationKind::Calls), vec![id2]);
271 }
272
273 #[test]
274 fn test_relation_kind() {
275 assert_ne!(RelationKind::Calls, RelationKind::CalledBy);
276 }
277}