codanna/parsing/resolution.rs
1//! Language-agnostic resolution traits for symbol and inheritance resolution
2//!
3//! This module provides the trait abstractions that allow each language to implement
4//! its own resolution logic while keeping the indexer language-agnostic.
5
6use super::LanguageId;
7use super::context::ScopeType;
8use crate::types::Range;
9use crate::{FileId, SymbolId, parsing::Import};
10use std::collections::HashMap;
11
12/// Scope levels that work across all languages
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub enum ScopeLevel {
15 /// Function/block scope (local variables, parameters)
16 Local,
17 /// Module/file scope (module-level definitions)
18 Module,
19 /// Package/namespace scope (package-level visibility)
20 Package,
21 /// Global/project scope (public exports)
22 Global,
23}
24
25/// Classification of where an import originates from
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ImportOrigin {
28 /// The import refers to code defined within the current project/crate
29 Internal,
30 /// The import comes from an external dependency that is not indexed
31 External,
32 /// The origin could not be determined
33 Unknown,
34}
35
36/// Binding information for an import exposed to a file
37#[derive(Debug, Clone)]
38pub struct ImportBinding {
39 /// Original import record extracted from the parser or index
40 pub import: Import,
41 /// The name that becomes visible in the file (alias or trailing segment)
42 pub exposed_name: String,
43 /// Where the import originates from
44 pub origin: ImportOrigin,
45 /// Resolved local symbol, if any
46 pub resolved_symbol: Option<SymbolId>,
47}
48
49/// Language-agnostic resolution scope
50///
51/// Each language implements this trait to provide its own scoping rules.
52/// For example:
53/// - Rust: local -> imports -> module -> crate
54/// - Python: LEGB (Local, Enclosing, Global, Built-in)
55/// - TypeScript: hoisting, namespaces, type vs value space
56pub trait ResolutionScope: Send + Sync {
57 /// Add a symbol to the scope at the specified level
58 fn add_symbol(&mut self, name: String, symbol_id: SymbolId, scope_level: ScopeLevel);
59
60 /// Resolve a symbol name according to language-specific rules
61 fn resolve(&self, name: &str) -> Option<SymbolId>;
62
63 /// Clear the local scope (e.g., when exiting a function)
64 fn clear_local_scope(&mut self);
65
66 /// Enter a new scope (e.g., entering a function or block)
67 fn enter_scope(&mut self, scope_type: ScopeType);
68
69 /// Exit the current scope
70 fn exit_scope(&mut self);
71
72 /// Get all symbols currently in scope (for debugging)
73 fn symbols_in_scope(&self) -> Vec<(String, SymbolId, ScopeLevel)>;
74
75 /// Get as Any for downcasting (needed for language-specific operations)
76 fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
77
78 /// Resolve a relationship-aware symbol reference
79 ///
80 /// This method allows languages to handle relationship-specific resolution logic.
81 /// For example, Rust needs special handling for trait method definitions vs
82 /// inherent method definitions.
83 ///
84 /// Default implementation just delegates to standard resolve() for backward compatibility.
85 ///
86 /// # Parameters
87 /// - `from_name`: The source symbol name (e.g., "Display" for trait)
88 /// - `to_name`: The target symbol name (e.g., "fmt" for method)
89 /// - `kind`: The relationship kind (Defines, Calls, Implements, etc.)
90 /// - `from_file`: The file where the relationship originates
91 ///
92 /// # Returns
93 /// The resolved SymbolId if found, None otherwise
94 fn resolve_relationship(
95 &self,
96 from_name: &str,
97 to_name: &str,
98 kind: crate::RelationKind,
99 from_file: FileId,
100 ) -> Option<SymbolId> {
101 // Default: use standard resolution
102 // Languages can override for relationship-specific logic
103 let _ = (from_name, kind, from_file); // Unused in default impl
104 self.resolve(to_name)
105 }
106
107 /// Populate import records into the resolution context
108 ///
109 /// Called during context building to load import statements from the index.
110 /// Each language converts Import records into their internal representation
111 /// for later use in is_external_import() checks.
112 ///
113 /// # Parameters
114 /// - `imports`: List of Import records from the index for this file
115 ///
116 /// # Default Behavior
117 /// Does nothing - languages that need import tracking must override this.
118 ///
119 /// # Example
120 /// ```rust,ignore
121 /// // Rust context would call add_import() for each Import record
122 /// for import in imports {
123 /// rust_context.add_import(import.path, import.alias);
124 /// }
125 /// ```
126 fn populate_imports(&mut self, imports: &[crate::parsing::Import]) {
127 let _ = imports; // Unused in default impl
128 }
129
130 /// Register a processed import binding for later queries
131 ///
132 /// Default implementation ignores the binding. Languages that need import-aware
133 /// resolution should override to record the binding.
134 fn register_import_binding(&mut self, binding: ImportBinding) {
135 let _ = binding;
136 }
137
138 /// Retrieve a previously registered import binding by exposed name
139 ///
140 /// Implementations should return a cloned binding if one exists. Default returns None.
141 fn import_binding(&self, _name: &str) -> Option<ImportBinding> {
142 None
143 }
144
145 /// Resolve an expression (e.g., receiver) to a concrete type string if available
146 ///
147 /// Default implementation returns None. Languages can override to provide
148 /// richer resolution data (e.g., Kotlin generic flow).
149 fn resolve_expression_type(&self, _expr: &str) -> Option<String> {
150 None
151 }
152
153 /// Check if a name refers to an external import
154 ///
155 /// This method determines if a symbol name comes from an external library
156 /// or package that is not indexed in the current codebase.
157 ///
158 /// When true, the indexer will not attempt to resolve methods or members
159 /// of this symbol, preventing incorrect resolution to local symbols with
160 /// the same name.
161 ///
162 /// # Parameters
163 /// - `name`: The symbol name to check (e.g., "ProgressBar", "React")
164 ///
165 /// # Returns
166 /// true if the name is from an external import with no local symbol_id
167 ///
168 /// # Default Behavior
169 /// Returns false - assumes all symbols are local or can be resolved.
170 /// Languages should override this to check their import lists.
171 ///
172 /// # Example
173 /// ```rust,ignore
174 /// // Rust: use indicatif::ProgressBar;
175 /// context.is_external_import("ProgressBar") // true - external crate
176 /// context.is_external_import("MyStruct") // false - local symbol
177 /// ```
178 fn is_external_import(&self, name: &str) -> bool {
179 if let Some(binding) = self.import_binding(name) {
180 match binding.origin {
181 ImportOrigin::External => true,
182 ImportOrigin::Internal => binding.resolved_symbol.is_none(),
183 ImportOrigin::Unknown => binding.resolved_symbol.is_none(),
184 }
185 } else {
186 false
187 }
188 }
189
190 /// Check if a relationship between two symbol kinds is valid
191 ///
192 /// This method defines which relationships are semantically valid for a language.
193 /// For example, in most languages a Function can Call another Function, but
194 /// may not be able to Call a Constant. Languages override this to define
195 /// their specific semantics (e.g., TypeScript allowing Constants to be callable
196 /// for React components).
197 ///
198 /// # Parameters
199 /// - `from_kind`: The kind of the source symbol
200 /// - `to_kind`: The kind of the target symbol
201 /// - `rel_kind`: The type of relationship
202 ///
203 /// # Returns
204 /// true if the relationship is valid, false otherwise
205 fn is_compatible_relationship(
206 &self,
207 from_kind: crate::SymbolKind,
208 to_kind: crate::SymbolKind,
209 rel_kind: crate::RelationKind,
210 ) -> bool {
211 use crate::RelationKind::*;
212 use crate::SymbolKind::*;
213
214 match rel_kind {
215 Calls => {
216 // Executable code can call other executable code
217 let caller_can_call = matches!(from_kind, Function | Method | Macro | Module);
218 let callee_can_be_called = matches!(to_kind, Function | Method | Macro | Class);
219 caller_can_call && callee_can_be_called
220 }
221 CalledBy => {
222 // Reverse of Calls
223 let caller_can_call = matches!(to_kind, Function | Method | Macro | Module);
224 let callee_can_be_called = matches!(from_kind, Function | Method | Macro | Class);
225 callee_can_be_called && caller_can_call
226 }
227 Implements => {
228 // Types can implement interfaces/traits
229 matches!(from_kind, Struct | Enum | Class) && matches!(to_kind, Trait | Interface)
230 }
231 ImplementedBy => {
232 // Reverse of Implements
233 matches!(from_kind, Trait | Interface) && matches!(to_kind, Struct | Enum | Class)
234 }
235 Uses => {
236 // Most symbols can use/reference types and values
237 let can_use = matches!(
238 from_kind,
239 Function | Method | Struct | Class | Trait | Interface | Module | Enum
240 );
241 let can_be_used = matches!(
242 to_kind,
243 Struct
244 | Enum
245 | Class
246 | Trait
247 | Interface
248 | TypeAlias
249 | Constant
250 | Variable
251 | Function
252 | Method
253 );
254
255 can_use && can_be_used
256 }
257 UsedBy => {
258 // Reverse of Uses
259 let can_use = matches!(
260 to_kind,
261 Function | Method | Struct | Class | Trait | Interface | Module | Enum
262 );
263 let can_be_used = matches!(
264 from_kind,
265 Struct
266 | Enum
267 | Class
268 | Trait
269 | Interface
270 | TypeAlias
271 | Constant
272 | Variable
273 | Function
274 | Method
275 );
276 can_be_used && can_use
277 }
278 Defines => {
279 // Containers can define members
280 let container = matches!(
281 from_kind,
282 Trait | Interface | Module | Struct | Enum | Class
283 );
284 let member = matches!(to_kind, Method | Function | Constant | Field | Variable);
285 container && member
286 }
287 DefinedIn => {
288 // Reverse of Defines
289 let member = matches!(from_kind, Method | Function | Constant | Field | Variable);
290 let container =
291 matches!(to_kind, Trait | Interface | Module | Struct | Enum | Class);
292 member && container
293 }
294 Extends => {
295 // Types can extend other types (inheritance)
296 let extendable = matches!(from_kind, Class | Interface | Trait | Struct | Enum);
297 let can_be_extended = matches!(to_kind, Class | Interface | Trait | Struct | Enum);
298 extendable && can_be_extended
299 }
300 ExtendedBy => {
301 // Reverse of Extends
302 matches!(from_kind, Class | Interface | Trait | Struct | Enum)
303 && matches!(to_kind, Class | Interface | Trait | Struct | Enum)
304 }
305 References => {
306 // Very permissive - almost anything can reference anything
307 true
308 }
309 ReferencedBy => {
310 // Reverse of References - also permissive
311 true
312 }
313 }
314 }
315}
316
317/// Project-specific resolution enhancement
318///
319/// This trait allows languages to enhance their import resolution with
320/// project configuration (tsconfig.json, pyproject.toml, go.mod, etc.)
321pub trait ProjectResolutionEnhancer: Send + Sync {
322 /// Transform an import path using project-specific rules
323 ///
324 /// Returns None if no transformation is needed (use original path)
325 /// Returns Some(enhanced_path) if the import should be resolved differently
326 ///
327 /// # Examples
328 /// - TypeScript: "@app/utils" -> "src/app/utils"
329 /// - Python: ".utils" -> "mypackage.submodule.utils"
330 /// - Go: "old/pkg" -> "../new/pkg"
331 /// - PHP: "App\Utils" -> "src/Utils.php"
332 fn enhance_import_path(&self, import_path: &str, from_file: FileId) -> Option<String>;
333
334 /// Get all possible candidate paths for an import
335 ///
336 /// Some project configs support multiple target paths (e.g., TypeScript paths)
337 fn get_import_candidates(&self, import_path: &str, from_file: FileId) -> Vec<String> {
338 // Default: single enhancement or original
339 if let Some(enhanced) = self.enhance_import_path(import_path, from_file) {
340 vec![enhanced]
341 } else {
342 vec![import_path.to_string()]
343 }
344 }
345}
346
347/// Language-agnostic inheritance resolver
348///
349/// Each language implements this trait to handle its inheritance model:
350/// - Rust: traits and inherent implementations
351/// - TypeScript: interfaces and class extension
352/// - Python: multiple inheritance with MRO
353/// - PHP: traits and interfaces
354/// - Go: interfaces and struct embedding
355pub trait InheritanceResolver: Send + Sync {
356 /// Add an inheritance relationship
357 fn add_inheritance(&mut self, child: String, parent: String, kind: &str);
358
359 /// Resolve which parent provides a method
360 fn resolve_method(&self, type_name: &str, method: &str) -> Option<String>;
361
362 /// Get the inheritance chain for a type
363 fn get_inheritance_chain(&self, type_name: &str) -> Vec<String>;
364
365 /// Check if one type is a subtype of another
366 fn is_subtype(&self, child: &str, parent: &str) -> bool;
367
368 /// Add methods that a type defines
369 fn add_type_methods(&mut self, type_name: String, methods: Vec<String>);
370
371 /// Get all methods available on a type (including inherited)
372 fn get_all_methods(&self, type_name: &str) -> Vec<String>;
373}
374
375/// Generic resolution context that wraps the existing ResolutionContext
376///
377/// This provides a default implementation that maintains backward compatibility
378/// while allowing languages to override with their own logic.
379pub struct GenericResolutionContext {
380 #[allow(dead_code)]
381 file_id: FileId, // Kept for future use when we need file-specific resolution
382 symbols: HashMap<ScopeLevel, HashMap<String, SymbolId>>,
383 scope_stack: Vec<ScopeType>,
384 import_bindings: HashMap<String, ImportBinding>,
385}
386
387impl GenericResolutionContext {
388 /// Create a new generic resolution context
389 pub fn new(file_id: FileId) -> Self {
390 let mut symbols = HashMap::new();
391 symbols.insert(ScopeLevel::Local, HashMap::new());
392 symbols.insert(ScopeLevel::Module, HashMap::new());
393 symbols.insert(ScopeLevel::Package, HashMap::new());
394 symbols.insert(ScopeLevel::Global, HashMap::new());
395
396 Self {
397 file_id,
398 symbols,
399 scope_stack: vec![ScopeType::Global],
400 import_bindings: HashMap::new(),
401 }
402 }
403
404 /// Wrap an existing ResolutionContext (for migration)
405 pub fn from_existing(file_id: FileId) -> Self {
406 Self::new(file_id)
407 }
408}
409
410impl ResolutionScope for GenericResolutionContext {
411 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
412 self
413 }
414
415 fn add_symbol(&mut self, name: String, symbol_id: SymbolId, scope_level: ScopeLevel) {
416 self.symbols
417 .entry(scope_level)
418 .or_default()
419 .insert(name, symbol_id);
420 }
421
422 fn resolve(&self, name: &str) -> Option<SymbolId> {
423 // Check scopes in order: Local -> Module -> Package -> Global
424 for level in &[
425 ScopeLevel::Local,
426 ScopeLevel::Module,
427 ScopeLevel::Package,
428 ScopeLevel::Global,
429 ] {
430 if let Some(symbols) = self.symbols.get(level) {
431 if let Some(&id) = symbols.get(name) {
432 return Some(id);
433 }
434 }
435 }
436 None
437 }
438
439 fn clear_local_scope(&mut self) {
440 if let Some(local) = self.symbols.get_mut(&ScopeLevel::Local) {
441 local.clear();
442 }
443 }
444
445 fn enter_scope(&mut self, scope_type: ScopeType) {
446 self.scope_stack.push(scope_type);
447 }
448
449 fn exit_scope(&mut self) {
450 self.scope_stack.pop();
451 // Clear local scope when exiting a function
452 if matches!(self.scope_stack.last(), Some(ScopeType::Function { .. })) {
453 self.clear_local_scope();
454 }
455 }
456
457 fn symbols_in_scope(&self) -> Vec<(String, SymbolId, ScopeLevel)> {
458 let mut result = Vec::new();
459 for (&level, symbols) in &self.symbols {
460 for (name, &id) in symbols {
461 result.push((name.clone(), id, level));
462 }
463 }
464 result
465 }
466
467 fn register_import_binding(&mut self, binding: ImportBinding) {
468 self.import_bindings
469 .insert(binding.exposed_name.clone(), binding);
470 }
471
472 fn import_binding(&self, name: &str) -> Option<ImportBinding> {
473 self.import_bindings.get(name).cloned()
474 }
475}
476
477/// Generic inheritance resolver that provides default implementation
478///
479/// This wraps existing trait resolution logic while allowing languages
480/// to provide their own inheritance semantics.
481pub struct GenericInheritanceResolver {
482 /// Maps child to parent relationships
483 inheritance: HashMap<String, Vec<(String, String)>>, // (parent, kind)
484 /// Maps types to their methods
485 type_methods: HashMap<String, Vec<String>>,
486}
487
488impl GenericInheritanceResolver {
489 /// Create a new generic inheritance resolver
490 pub fn new() -> Self {
491 Self {
492 inheritance: HashMap::new(),
493 type_methods: HashMap::new(),
494 }
495 }
496}
497
498impl Default for GenericInheritanceResolver {
499 fn default() -> Self {
500 Self::new()
501 }
502}
503
504impl InheritanceResolver for GenericInheritanceResolver {
505 fn add_inheritance(&mut self, child: String, parent: String, kind: &str) {
506 self.inheritance
507 .entry(child)
508 .or_default()
509 .push((parent, kind.to_string()));
510 }
511
512 fn resolve_method(&self, type_name: &str, method: &str) -> Option<String> {
513 // First check if the type has the method directly
514 if let Some(methods) = self.type_methods.get(type_name) {
515 if methods.contains(&method.to_string()) {
516 return Some(type_name.to_string());
517 }
518 }
519
520 // Then check parent types
521 if let Some(parents) = self.inheritance.get(type_name) {
522 for (parent, _kind) in parents {
523 if let Some(result) = self.resolve_method(parent, method) {
524 return Some(result);
525 }
526 }
527 }
528
529 None
530 }
531
532 fn get_inheritance_chain(&self, type_name: &str) -> Vec<String> {
533 let mut chain = vec![type_name.to_string()];
534 let mut visited = std::collections::HashSet::new();
535 visited.insert(type_name.to_string());
536
537 let mut to_visit = vec![type_name.to_string()];
538
539 while let Some(current) = to_visit.pop() {
540 if let Some(parents) = self.inheritance.get(¤t) {
541 for (parent, _kind) in parents {
542 if visited.insert(parent.clone()) {
543 chain.push(parent.clone());
544 to_visit.push(parent.clone());
545 }
546 }
547 }
548 }
549
550 chain
551 }
552
553 fn is_subtype(&self, child: &str, parent: &str) -> bool {
554 if child == parent {
555 return true;
556 }
557
558 let chain = self.get_inheritance_chain(child);
559 chain.contains(&parent.to_string())
560 }
561
562 fn add_type_methods(&mut self, type_name: String, methods: Vec<String>) {
563 self.type_methods.insert(type_name, methods);
564 }
565
566 fn get_all_methods(&self, type_name: &str) -> Vec<String> {
567 let mut methods = Vec::new();
568 let chain = self.get_inheritance_chain(type_name);
569
570 for ancestor in chain {
571 if let Some(type_methods) = self.type_methods.get(&ancestor) {
572 for method in type_methods {
573 if !methods.contains(method) {
574 methods.push(method.clone());
575 }
576 }
577 }
578 }
579
580 methods
581 }
582}
583
584// ═══════════════════════════════════════════════════════════════════════════
585// Pipeline Symbol Cache Trait
586// ═══════════════════════════════════════════════════════════════════════════
587
588/// Context about the calling symbol for visibility and scope resolution.
589///
590/// Used by `PipelineSymbolCache::resolve()` to determine what the caller can see.
591/// Three-level visibility model:
592/// - Same file: always visible
593/// - Same module: always visible (module_path prefix match)
594/// - Different module: must be Public
595///
596/// # Example
597/// ```ignore
598/// let caller = CallerContext {
599/// file_id: file_100,
600/// module_path: Some("src.components".into()),
601/// language_id: LanguageId::new("typescript"),
602/// };
603/// let result = cache.resolve("Button", &caller, None, &imports);
604/// ```
605#[derive(Debug, Clone)]
606pub struct CallerContext {
607 /// File where the call/reference originates
608 pub file_id: FileId,
609 /// Module path of the calling symbol (for same-module visibility check)
610 pub module_path: Option<Box<str>>,
611 /// Language of the calling code (for cross-language filtering)
612 pub language_id: LanguageId,
613}
614
615impl CallerContext {
616 /// Create caller context with explicit values.
617 pub fn new(file_id: FileId, module_path: Option<Box<str>>, language_id: LanguageId) -> Self {
618 Self {
619 file_id,
620 module_path,
621 language_id,
622 }
623 }
624
625 /// Create caller context from file and language (no module path).
626 pub fn from_file(file_id: FileId, language_id: LanguageId) -> Self {
627 Self {
628 file_id,
629 module_path: None,
630 language_id,
631 }
632 }
633
634 /// Check if caller is in the same module as a target symbol.
635 ///
636 /// Same module = module_path prefix match (e.g., "src.components" matches "src.components.Button").
637 /// Returns false if either lacks module_path (falls back to file check).
638 pub fn is_same_module(&self, target_module_path: Option<&str>) -> bool {
639 match (&self.module_path, target_module_path) {
640 (Some(caller), Some(target)) => {
641 // Same module if one is prefix of the other
642 caller.starts_with(target) || target.starts_with(caller.as_ref())
643 }
644 // If either lacks module_path, fall back to file check (done by caller)
645 _ => false,
646 }
647 }
648}
649
650/// Trait for symbol cache used in pipeline resolution.
651///
652/// Implemented by `SymbolLookupCache` (DashMap-based parallel pipeline cache).
653/// Provides methods for resolving imports and symbols without hitting Tantivy.
654pub trait PipelineSymbolCache: Send + Sync {
655 /// Multi-tier symbol resolution with proper priority order.
656 ///
657 /// Resolves a symbol reference using all available context to find the correct target.
658 /// Priority order ensures we pick the most likely match:
659 ///
660 /// 1. **Local**: Same file + matching name + defined before `to_range`
661 /// 2. **Import**: Name matches import alias or last segment of import path
662 /// 3. **Same module**: Module path prefix match (same package/namespace)
663 /// 4. **Cross-file**: Public symbol, same language
664 ///
665 /// # Parameters
666 /// - `name`: The symbol name to resolve (e.g., "helper", "UserService")
667 /// - `caller`: Context about the calling symbol (file, module, language)
668 /// - `to_range`: Optional source location of the reference (for local scope ordering)
669 /// - `imports`: Imports visible in the file (from CONTEXT stage)
670 ///
671 /// # Returns
672 /// - `Found(id)`: Unambiguous match
673 /// - `Ambiguous(ids)`: Multiple candidates, need more context
674 /// - `NotFound`: No matching symbol in cache
675 fn resolve(
676 &self,
677 name: &str,
678 caller: &CallerContext,
679 to_range: Option<&Range>,
680 imports: &[Import],
681 ) -> ResolveResult;
682
683 /// Direct lookup by ID for metadata access.
684 ///
685 /// Used after resolution to get full symbol details (module_path, kind, etc).
686 fn get(&self, id: SymbolId) -> Option<crate::Symbol>;
687
688 /// Get all symbols in a file (for local scope building).
689 ///
690 /// Returns symbol IDs for all definitions in the file.
691 fn symbols_in_file(&self, file_id: FileId) -> Vec<SymbolId>;
692
693 /// Get candidate symbol IDs by name.
694 ///
695 /// Returns all symbols with the given name for module path matching.
696 fn lookup_candidates(&self, name: &str) -> Vec<SymbolId>;
697}
698
699/// Result of multi-tier symbol resolution.
700#[derive(Debug, Clone, PartialEq, Eq)]
701pub enum ResolveResult {
702 /// Unambiguous match found.
703 Found(SymbolId),
704 /// Multiple candidates match - need more context to disambiguate.
705 Ambiguous(Vec<SymbolId>),
706 /// No matching symbol in cache.
707 NotFound,
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713
714 #[test]
715 fn test_default_compatibility_function_calls_function() {
716 let context = GenericResolutionContext::new(FileId::new(1).unwrap());
717 assert!(context.is_compatible_relationship(
718 crate::SymbolKind::Function,
719 crate::SymbolKind::Function,
720 crate::RelationKind::Calls
721 ));
722 }
723
724 #[test]
725 fn test_default_compatibility_function_cannot_call_constant() {
726 let context = GenericResolutionContext::new(FileId::new(1).unwrap());
727 assert!(!context.is_compatible_relationship(
728 crate::SymbolKind::Function,
729 crate::SymbolKind::Constant,
730 crate::RelationKind::Calls
731 ));
732 }
733
734 #[test]
735 fn test_default_compatibility_class_extends_class() {
736 let context = GenericResolutionContext::new(FileId::new(1).unwrap());
737 assert!(context.is_compatible_relationship(
738 crate::SymbolKind::Class,
739 crate::SymbolKind::Class,
740 crate::RelationKind::Extends
741 ));
742 }
743
744 #[test]
745 fn test_generic_resolution_context() {
746 let mut ctx = GenericResolutionContext::new(FileId::new(1).unwrap());
747
748 // Add symbols at different scope levels
749 ctx.add_symbol(
750 "local_var".to_string(),
751 SymbolId::new(1).unwrap(),
752 ScopeLevel::Local,
753 );
754 ctx.add_symbol(
755 "module_fn".to_string(),
756 SymbolId::new(2).unwrap(),
757 ScopeLevel::Module,
758 );
759 ctx.add_symbol(
760 "global_type".to_string(),
761 SymbolId::new(3).unwrap(),
762 ScopeLevel::Global,
763 );
764
765 // Test resolution order
766 assert_eq!(ctx.resolve("local_var"), Some(SymbolId::new(1).unwrap()));
767 assert_eq!(ctx.resolve("module_fn"), Some(SymbolId::new(2).unwrap()));
768 assert_eq!(ctx.resolve("global_type"), Some(SymbolId::new(3).unwrap()));
769 assert_eq!(ctx.resolve("unknown"), None);
770
771 // Test scope clearing
772 ctx.clear_local_scope();
773 assert_eq!(ctx.resolve("local_var"), None);
774 assert_eq!(ctx.resolve("module_fn"), Some(SymbolId::new(2).unwrap()));
775 }
776
777 #[test]
778 fn test_generic_inheritance_resolver() {
779 let mut resolver = GenericInheritanceResolver::new();
780
781 // Set up a simple inheritance hierarchy
782 resolver.add_inheritance("Child".to_string(), "Parent".to_string(), "extends");
783 resolver.add_inheritance("Parent".to_string(), "GrandParent".to_string(), "extends");
784
785 // Add methods
786 resolver.add_type_methods("GrandParent".to_string(), vec!["method1".to_string()]);
787 resolver.add_type_methods("Parent".to_string(), vec!["method2".to_string()]);
788 resolver.add_type_methods("Child".to_string(), vec!["method3".to_string()]);
789
790 // Test method resolution
791 assert_eq!(
792 resolver.resolve_method("Child", "method3"),
793 Some("Child".to_string())
794 );
795 assert_eq!(
796 resolver.resolve_method("Child", "method2"),
797 Some("Parent".to_string())
798 );
799 assert_eq!(
800 resolver.resolve_method("Child", "method1"),
801 Some("GrandParent".to_string())
802 );
803
804 // Test inheritance chain
805 let chain = resolver.get_inheritance_chain("Child");
806 assert!(chain.contains(&"Child".to_string()));
807 assert!(chain.contains(&"Parent".to_string()));
808 assert!(chain.contains(&"GrandParent".to_string()));
809
810 // Test subtype checking
811 assert!(resolver.is_subtype("Child", "Parent"));
812 assert!(resolver.is_subtype("Child", "GrandParent"));
813 assert!(!resolver.is_subtype("Parent", "Child"));
814 }
815}