1use super::{
4 DiscoveredSymbol, DiscoveryQuery, DiscoveryResult, RelationGraph, RelationKind, SortOrder,
5 TypeFilter,
6};
7use crate::query::{CodeEdgeV2, CodeGraphV2, DataFlowGraphV2, TypeFlowGraphV2, UsageContext};
8use crate::symbol::{SymbolId, SymbolRegistry};
9use crate::Pattern;
10use crate::SymbolKind;
11
12pub struct DiscoveryEngine<'a> {
28 graph: &'a CodeGraphV2,
29 registry: &'a SymbolRegistry,
30 _dataflow: Option<&'a DataFlowGraphV2>,
31 typeflow: Option<&'a TypeFlowGraphV2>,
32}
33
34impl<'a> DiscoveryEngine<'a> {
35 pub fn new(
37 graph: &'a CodeGraphV2,
38 registry: &'a SymbolRegistry,
39 dataflow: Option<&'a DataFlowGraphV2>,
40 ) -> Self {
41 Self {
42 graph,
43 registry,
44 _dataflow: dataflow,
45 typeflow: None,
46 }
47 }
48
49 pub fn set_typeflow(mut self, typeflow: &'a TypeFlowGraphV2) -> Self {
51 self.typeflow = Some(typeflow);
52 self
53 }
54
55 pub fn execute(&self, query: &DiscoveryQuery) -> DiscoveryResult {
57 let mut result = DiscoveryResult::new();
58 let mut matches = Vec::new();
59
60 for (id, path) in self.registry.iter() {
62 if !query.pattern.matches(path.name()) {
64 continue;
65 }
66
67 if let Some(ref crate_name) = query.in_crate {
69 if path.crate_name() != crate_name {
70 continue;
71 }
72 }
73
74 if let Some(ref module) = query.in_module {
76 if !path.to_string().contains(module) {
77 continue;
78 }
79 }
80
81 if let Some(ref kinds) = query.kinds {
83 if let Some(kind) = self.registry.kind(id) {
84 if !kinds.iter().any(|k| k.matches(&kind)) {
85 continue;
86 }
87 } else {
88 continue;
89 }
90 }
91
92 let kind = self.registry.kind(id).unwrap_or(SymbolKind::Other);
94 let mut symbol = DiscoveredSymbol::new(id, path.clone(), kind);
95
96 if let Some(uuid) = self.registry.uuid(id) {
98 symbol = symbol.with_uuid(uuid);
99 }
100
101 if let Some(span) = self.registry.span(id) {
103 symbol = symbol.with_span(span.clone());
104 }
105 if let Some(vis) = self.registry.visibility(id) {
106 symbol = symbol.with_visibility(vis.clone());
107 }
108
109 let score = self.calculate_score(path.name(), &query.pattern);
111 let ref_count = self.graph.reference_count(id);
112 let impl_count = self.graph.impl_count(id);
113 symbol = symbol
114 .with_score(score)
115 .with_ref_count(ref_count)
116 .with_impl_count(impl_count);
117
118 matches.push(symbol);
119 }
120
121 if let (Some(ref type_filter), Some(typeflow)) = (&query.type_filter, self.typeflow) {
123 if !type_filter.is_empty() {
124 matches = self.apply_type_filter(matches, type_filter, typeflow);
125 }
126 }
127
128 match query.sort {
130 SortOrder::Refs => {
131 matches.sort_by(|a, b| {
133 b.ref_count.cmp(&a.ref_count).then_with(|| {
134 b.score
135 .partial_cmp(&a.score)
136 .unwrap_or(std::cmp::Ordering::Equal)
137 })
138 });
139 }
140 SortOrder::Alpha => {
141 matches.sort_by(|a, b| a.path.name().cmp(b.path.name()));
143 }
144 SortOrder::Impls => {
145 matches.sort_by(|a, b| {
147 b.impl_count
148 .cmp(&a.impl_count)
149 .then_with(|| b.ref_count.cmp(&a.ref_count))
150 });
151 }
152 }
153
154 result.total_matches = matches.len();
156 if let Some(limit) = query.limit {
157 if matches.len() > limit {
158 result.truncated = true;
159 matches.truncate(limit);
160 }
161 }
162
163 result.symbols = matches;
164
165 if query.include_relations && !result.is_empty() {
167 result.relations = Some(self.build_relations(&result, query.relation_depth));
168 }
169
170 result
171 }
172
173 fn calculate_score(&self, name: &str, pattern: &crate::Pattern) -> f32 {
175 let pattern_str = pattern.as_str();
176
177 if name == pattern_str {
179 return 1.0;
180 }
181
182 if pattern.is_exact() {
184 return 0.0; }
186
187 let base_score = 0.5;
189
190 let length_bonus = 1.0 / (name.len() as f32 + 1.0) * 0.2;
192
193 let prefix_bonus = if pattern_str.starts_with('*') {
195 let suffix = pattern_str.trim_start_matches('*');
197 if name.ends_with(suffix) {
198 0.2
199 } else {
200 0.0
201 }
202 } else if pattern_str.ends_with('*') {
203 let prefix = pattern_str.trim_end_matches('*');
205 if name.starts_with(prefix) {
206 0.2
207 } else {
208 0.0
209 }
210 } else {
211 0.0
212 };
213
214 base_score + length_bonus + prefix_bonus
215 }
216
217 fn build_relations(&self, result: &DiscoveryResult, max_depth: usize) -> RelationGraph {
219 let mut graph = RelationGraph::new();
220
221 for symbol in result.iter() {
222 self.add_relations(&mut graph, symbol.id, max_depth, 0);
223 }
224
225 graph
226 }
227
228 fn add_relations(
230 &self,
231 graph: &mut RelationGraph,
232 id: crate::symbol::SymbolId,
233 max_depth: usize,
234 current_depth: usize,
235 ) {
236 if current_depth >= max_depth {
237 return;
238 }
239
240 if !self.graph.contains(id) {
242 return;
243 }
244
245 for edge_data in self.graph.outgoing_edges(id) {
247 let target_id = edge_data.to;
248 let kind = match edge_data.kind {
249 CodeEdgeV2::Calls => RelationKind::Calls,
250 CodeEdgeV2::Implements => RelationKind::Implements,
251 CodeEdgeV2::Contains => RelationKind::Contains,
252 };
253 graph.add(id, target_id, kind);
254 }
255
256 for edge_data in self.graph.incoming_edges(id) {
258 let source_id = edge_data.from;
259 let kind = match edge_data.kind {
260 CodeEdgeV2::Calls => RelationKind::CalledBy,
261 CodeEdgeV2::Implements => RelationKind::ImplementedBy,
262 CodeEdgeV2::Contains => RelationKind::ContainedBy,
263 };
264 graph.add(id, source_id, kind);
265 }
266
267 if let Some(typeflow) = self.typeflow {
269 for target_id in typeflow.types_used_by(id) {
271 graph.add(id, target_id, RelationKind::TypeReferences);
272 }
273 for user_id in typeflow.type_users(id) {
275 graph.add(id, user_id, RelationKind::TypeReferencedBy);
276 }
277 }
278 }
279
280 pub fn find_exact(&self, name: &str) -> DiscoveryResult {
282 self.execute(&DiscoveryQuery::exact(name))
283 }
284
285 pub fn find_glob(&self, pattern: &str) -> DiscoveryResult {
287 self.execute(&DiscoveryQuery::symbol(pattern))
288 }
289
290 pub fn find_implementations(
292 &self,
293 trait_id: crate::symbol::SymbolId,
294 ) -> Vec<crate::symbol::SymbolId> {
295 self.graph.implementors_of(trait_id).collect()
296 }
297
298 pub fn find_callers(&self, func_id: crate::symbol::SymbolId) -> Vec<crate::symbol::SymbolId> {
300 self.graph.callers_of(func_id).collect()
301 }
302
303 pub fn find_callees(&self, func_id: crate::symbol::SymbolId) -> Vec<crate::symbol::SymbolId> {
305 self.graph.callees_of(func_id).collect()
306 }
307
308 fn apply_type_filter(
314 &self,
315 symbols: Vec<DiscoveredSymbol>,
316 filter: &TypeFilter,
317 typeflow: &TypeFlowGraphV2,
318 ) -> Vec<DiscoveredSymbol> {
319 symbols
320 .into_iter()
321 .filter(|symbol| self.matches_type_filter(symbol.id, filter, typeflow))
322 .collect()
323 }
324
325 fn matches_type_filter(
327 &self,
328 id: SymbolId,
329 filter: &TypeFilter,
330 typeflow: &TypeFlowGraphV2,
331 ) -> bool {
332 if let Some(ref pattern) = filter.return_type {
334 if !self.has_matching_usage_v2(id, UsageContext::ReturnType, pattern, typeflow) {
335 return false;
336 }
337 }
338
339 if let Some(ref pattern) = filter.param_type {
341 if !self.has_matching_usage_v2(id, UsageContext::ParamType, pattern, typeflow) {
342 return false;
343 }
344 }
345
346 if let Some(ref pattern) = filter.field_type {
348 let has_field =
349 self.has_matching_usage_v2(id, UsageContext::FieldType, pattern, typeflow)
350 || self.has_matching_usage_v2(
351 id,
352 UsageContext::VariantField,
353 pattern,
354 typeflow,
355 );
356 if !has_field {
357 return false;
358 }
359 }
360
361 if let Some(ref pattern) = filter.uses_type {
363 if !self.has_any_matching_usage_v2(id, pattern, typeflow) {
364 return false;
365 }
366 }
367
368 if let Some(ref pattern) = filter.has_bound {
370 if !self.has_matching_bound_v2(id, pattern, typeflow) {
371 return false;
372 }
373 }
374
375 true
376 }
377
378 fn has_matching_usage_v2(
380 &self,
381 id: SymbolId,
382 expected_context: UsageContext,
383 pattern: &Pattern,
384 typeflow: &TypeFlowGraphV2,
385 ) -> bool {
386 for usage_id in typeflow.usages(id) {
387 if let Some(usage_data) = typeflow.get_usage(usage_id) {
388 if usage_data.context != expected_context {
389 continue;
390 }
391 if let Some(resolved_id) = usage_data.resolved {
393 if let Some(path) = self.registry.resolve(resolved_id) {
394 if pattern.matches(path.name()) {
395 return true;
396 }
397 }
398 }
399 }
400 }
401 false
402 }
403
404 fn has_any_matching_usage_v2(
406 &self,
407 id: SymbolId,
408 pattern: &Pattern,
409 typeflow: &TypeFlowGraphV2,
410 ) -> bool {
411 for usage_id in typeflow.usages(id) {
412 if let Some(usage_data) = typeflow.get_usage(usage_id) {
413 if let Some(resolved_id) = usage_data.resolved {
415 if let Some(path) = self.registry.resolve(resolved_id) {
416 if pattern.matches(path.name()) {
417 return true;
418 }
419 }
420 }
421 }
422 }
423 false
424 }
425
426 fn has_matching_bound_v2(
428 &self,
429 id: SymbolId,
430 pattern: &Pattern,
431 typeflow: &TypeFlowGraphV2,
432 ) -> bool {
433 let Some(def_node) = typeflow.definition(id) else {
435 return false;
436 };
437
438 for bound_id in typeflow.trait_bounds(def_node) {
440 if let Some(bound_data) = typeflow.get_trait_bound(bound_id) {
441 if let Some(trait_id) = bound_data.trait_id {
443 if let Some(path) = self.registry.resolve(trait_id) {
444 if pattern.matches(path.name()) {
445 return true;
446 }
447 }
448 }
449 }
450 }
451
452 false
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use crate::symbol::SymbolPath;
460 use crate::SymbolKind;
461
462 fn setup() -> (SymbolRegistry, CodeGraphV2) {
463 let mut registry = SymbolRegistry::new();
464
465 registry
466 .register(
467 SymbolPath::parse("mylib::AppConfig").unwrap(),
468 SymbolKind::Struct,
469 )
470 .unwrap();
471 registry
472 .register(
473 SymbolPath::parse("mylib::UserConfig").unwrap(),
474 SymbolKind::Struct,
475 )
476 .unwrap();
477 registry
478 .register(
479 SymbolPath::parse("mylib::handlers::handle").unwrap(),
480 SymbolKind::Function,
481 )
482 .unwrap();
483 registry
484 .register(
485 SymbolPath::parse("other::Config").unwrap(),
486 SymbolKind::Struct,
487 )
488 .unwrap();
489
490 let graph = CodeGraphV2::new();
491
492 (registry, graph)
493 }
494
495 #[test]
496 fn test_find_by_pattern() {
497 let (registry, graph) = setup();
498 let engine = DiscoveryEngine::new(&graph, ®istry, None);
499
500 let result = engine.find_glob("*Config");
501 assert_eq!(result.len(), 3); }
503
504 #[test]
505 fn test_find_with_crate_filter() {
506 let (registry, graph) = setup();
507 let engine = DiscoveryEngine::new(&graph, ®istry, None);
508
509 let query = DiscoveryQuery::symbol("*Config").in_crate("mylib");
510 let result = engine.execute(&query);
511 assert_eq!(result.len(), 2); }
513
514 #[test]
515 fn test_find_with_kind_filter() {
516 let (registry, graph) = setup();
517 let engine = DiscoveryEngine::new(&graph, ®istry, None);
518
519 let query = DiscoveryQuery::symbol("*").kind(SymbolKind::Function);
520 let result = engine.execute(&query);
521 assert_eq!(result.len(), 1); }
523
524 #[test]
525 fn test_find_with_module_filter() {
526 let (registry, graph) = setup();
527 let engine = DiscoveryEngine::new(&graph, ®istry, None);
528
529 let query = DiscoveryQuery::symbol("*").in_module("handlers");
530 let result = engine.execute(&query);
531 assert_eq!(result.len(), 1); }
533
534 #[test]
535 fn test_find_exact() {
536 let (registry, graph) = setup();
537 let engine = DiscoveryEngine::new(&graph, ®istry, None);
538
539 let result = engine.find_exact("AppConfig");
540 assert_eq!(result.len(), 1);
541 assert_eq!(result.first().unwrap().path.name(), "AppConfig");
542 }
543
544 #[test]
545 fn test_find_with_limit() {
546 let (registry, graph) = setup();
547 let engine = DiscoveryEngine::new(&graph, ®istry, None);
548
549 let query = DiscoveryQuery::symbol("*").limit(2);
550 let result = engine.execute(&query);
551 assert_eq!(result.len(), 2);
552 assert!(result.truncated);
553 assert_eq!(result.total_matches, 4);
554 }
555}