Skip to main content

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(&current) {
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}