use super::LanguageId;
use super::context::ScopeType;
use crate::types::Range;
use crate::{FileId, SymbolId, parsing::Import};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ScopeLevel {
Local,
Module,
Package,
Global,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImportOrigin {
Internal,
External,
Unknown,
}
#[derive(Debug, Clone)]
pub struct ImportBinding {
pub import: Import,
pub exposed_name: String,
pub origin: ImportOrigin,
pub resolved_symbol: Option<SymbolId>,
}
pub trait ResolutionScope: Send + Sync {
fn add_symbol(&mut self, name: String, symbol_id: SymbolId, scope_level: ScopeLevel);
fn resolve(&self, name: &str) -> Option<SymbolId>;
fn clear_local_scope(&mut self);
fn enter_scope(&mut self, scope_type: ScopeType);
fn exit_scope(&mut self);
fn symbols_in_scope(&self) -> Vec<(String, SymbolId, ScopeLevel)>;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn resolve_relationship(
&self,
from_name: &str,
to_name: &str,
kind: crate::RelationKind,
from_file: FileId,
) -> Option<SymbolId> {
let _ = (from_name, kind, from_file); self.resolve(to_name)
}
fn populate_imports(&mut self, imports: &[crate::parsing::Import]) {
let _ = imports; }
fn register_import_binding(&mut self, binding: ImportBinding) {
let _ = binding;
}
fn import_binding(&self, _name: &str) -> Option<ImportBinding> {
None
}
fn resolve_expression_type(&self, _expr: &str) -> Option<String> {
None
}
fn is_external_import(&self, name: &str) -> bool {
if let Some(binding) = self.import_binding(name) {
match binding.origin {
ImportOrigin::External => true,
ImportOrigin::Internal => binding.resolved_symbol.is_none(),
ImportOrigin::Unknown => binding.resolved_symbol.is_none(),
}
} else {
false
}
}
fn is_compatible_relationship(
&self,
from_kind: crate::SymbolKind,
to_kind: crate::SymbolKind,
rel_kind: crate::RelationKind,
) -> bool {
use crate::RelationKind::*;
use crate::SymbolKind::*;
match rel_kind {
Calls => {
let caller_can_call = matches!(from_kind, Function | Method | Macro | Module);
let callee_can_be_called = matches!(to_kind, Function | Method | Macro | Class);
caller_can_call && callee_can_be_called
}
CalledBy => {
let caller_can_call = matches!(to_kind, Function | Method | Macro | Module);
let callee_can_be_called = matches!(from_kind, Function | Method | Macro | Class);
callee_can_be_called && caller_can_call
}
Implements => {
matches!(from_kind, Struct | Enum | Class) && matches!(to_kind, Trait | Interface)
}
ImplementedBy => {
matches!(from_kind, Trait | Interface) && matches!(to_kind, Struct | Enum | Class)
}
Uses => {
let can_use = matches!(
from_kind,
Function | Method | Struct | Class | Trait | Interface | Module | Enum
);
let can_be_used = matches!(
to_kind,
Struct
| Enum
| Class
| Trait
| Interface
| TypeAlias
| Constant
| Variable
| Function
| Method
);
can_use && can_be_used
}
UsedBy => {
let can_use = matches!(
to_kind,
Function | Method | Struct | Class | Trait | Interface | Module | Enum
);
let can_be_used = matches!(
from_kind,
Struct
| Enum
| Class
| Trait
| Interface
| TypeAlias
| Constant
| Variable
| Function
| Method
);
can_be_used && can_use
}
Defines => {
let container = matches!(
from_kind,
Trait | Interface | Module | Struct | Enum | Class
);
let member = matches!(to_kind, Method | Function | Constant | Field | Variable);
container && member
}
DefinedIn => {
let member = matches!(from_kind, Method | Function | Constant | Field | Variable);
let container =
matches!(to_kind, Trait | Interface | Module | Struct | Enum | Class);
member && container
}
Extends => {
let extendable = matches!(from_kind, Class | Interface | Trait | Struct | Enum);
let can_be_extended = matches!(to_kind, Class | Interface | Trait | Struct | Enum);
extendable && can_be_extended
}
ExtendedBy => {
matches!(from_kind, Class | Interface | Trait | Struct | Enum)
&& matches!(to_kind, Class | Interface | Trait | Struct | Enum)
}
References => {
true
}
ReferencedBy => {
true
}
}
}
}
pub trait ProjectResolutionEnhancer: Send + Sync {
fn enhance_import_path(&self, import_path: &str, from_file: FileId) -> Option<String>;
fn get_import_candidates(&self, import_path: &str, from_file: FileId) -> Vec<String> {
if let Some(enhanced) = self.enhance_import_path(import_path, from_file) {
vec![enhanced]
} else {
vec![import_path.to_string()]
}
}
}
pub trait InheritanceResolver: Send + Sync {
fn add_inheritance(&mut self, child: String, parent: String, kind: &str);
fn resolve_method(&self, type_name: &str, method: &str) -> Option<String>;
fn get_inheritance_chain(&self, type_name: &str) -> Vec<String>;
fn is_subtype(&self, child: &str, parent: &str) -> bool;
fn add_type_methods(&mut self, type_name: String, methods: Vec<String>);
fn get_all_methods(&self, type_name: &str) -> Vec<String>;
}
pub struct GenericResolutionContext {
#[allow(dead_code)]
file_id: FileId, symbols: HashMap<ScopeLevel, HashMap<String, SymbolId>>,
scope_stack: Vec<ScopeType>,
import_bindings: HashMap<String, ImportBinding>,
}
impl GenericResolutionContext {
pub fn new(file_id: FileId) -> Self {
let mut symbols = HashMap::new();
symbols.insert(ScopeLevel::Local, HashMap::new());
symbols.insert(ScopeLevel::Module, HashMap::new());
symbols.insert(ScopeLevel::Package, HashMap::new());
symbols.insert(ScopeLevel::Global, HashMap::new());
Self {
file_id,
symbols,
scope_stack: vec![ScopeType::Global],
import_bindings: HashMap::new(),
}
}
pub fn from_existing(file_id: FileId) -> Self {
Self::new(file_id)
}
}
impl ResolutionScope for GenericResolutionContext {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn add_symbol(&mut self, name: String, symbol_id: SymbolId, scope_level: ScopeLevel) {
self.symbols
.entry(scope_level)
.or_default()
.insert(name, symbol_id);
}
fn resolve(&self, name: &str) -> Option<SymbolId> {
for level in &[
ScopeLevel::Local,
ScopeLevel::Module,
ScopeLevel::Package,
ScopeLevel::Global,
] {
if let Some(symbols) = self.symbols.get(level) {
if let Some(&id) = symbols.get(name) {
return Some(id);
}
}
}
None
}
fn clear_local_scope(&mut self) {
if let Some(local) = self.symbols.get_mut(&ScopeLevel::Local) {
local.clear();
}
}
fn enter_scope(&mut self, scope_type: ScopeType) {
self.scope_stack.push(scope_type);
}
fn exit_scope(&mut self) {
self.scope_stack.pop();
if matches!(self.scope_stack.last(), Some(ScopeType::Function { .. })) {
self.clear_local_scope();
}
}
fn symbols_in_scope(&self) -> Vec<(String, SymbolId, ScopeLevel)> {
let mut result = Vec::new();
for (&level, symbols) in &self.symbols {
for (name, &id) in symbols {
result.push((name.clone(), id, level));
}
}
result
}
fn register_import_binding(&mut self, binding: ImportBinding) {
self.import_bindings
.insert(binding.exposed_name.clone(), binding);
}
fn import_binding(&self, name: &str) -> Option<ImportBinding> {
self.import_bindings.get(name).cloned()
}
}
pub struct GenericInheritanceResolver {
inheritance: HashMap<String, Vec<(String, String)>>, type_methods: HashMap<String, Vec<String>>,
}
impl GenericInheritanceResolver {
pub fn new() -> Self {
Self {
inheritance: HashMap::new(),
type_methods: HashMap::new(),
}
}
}
impl Default for GenericInheritanceResolver {
fn default() -> Self {
Self::new()
}
}
impl InheritanceResolver for GenericInheritanceResolver {
fn add_inheritance(&mut self, child: String, parent: String, kind: &str) {
self.inheritance
.entry(child)
.or_default()
.push((parent, kind.to_string()));
}
fn resolve_method(&self, type_name: &str, method: &str) -> Option<String> {
if let Some(methods) = self.type_methods.get(type_name) {
if methods.contains(&method.to_string()) {
return Some(type_name.to_string());
}
}
if let Some(parents) = self.inheritance.get(type_name) {
for (parent, _kind) in parents {
if let Some(result) = self.resolve_method(parent, method) {
return Some(result);
}
}
}
None
}
fn get_inheritance_chain(&self, type_name: &str) -> Vec<String> {
let mut chain = vec![type_name.to_string()];
let mut visited = std::collections::HashSet::new();
visited.insert(type_name.to_string());
let mut to_visit = vec![type_name.to_string()];
while let Some(current) = to_visit.pop() {
if let Some(parents) = self.inheritance.get(¤t) {
for (parent, _kind) in parents {
if visited.insert(parent.clone()) {
chain.push(parent.clone());
to_visit.push(parent.clone());
}
}
}
}
chain
}
fn is_subtype(&self, child: &str, parent: &str) -> bool {
if child == parent {
return true;
}
let chain = self.get_inheritance_chain(child);
chain.contains(&parent.to_string())
}
fn add_type_methods(&mut self, type_name: String, methods: Vec<String>) {
self.type_methods.insert(type_name, methods);
}
fn get_all_methods(&self, type_name: &str) -> Vec<String> {
let mut methods = Vec::new();
let chain = self.get_inheritance_chain(type_name);
for ancestor in chain {
if let Some(type_methods) = self.type_methods.get(&ancestor) {
for method in type_methods {
if !methods.contains(method) {
methods.push(method.clone());
}
}
}
}
methods
}
}
#[derive(Debug, Clone)]
pub struct CallerContext {
pub file_id: FileId,
pub module_path: Option<Box<str>>,
pub language_id: LanguageId,
}
impl CallerContext {
pub fn new(file_id: FileId, module_path: Option<Box<str>>, language_id: LanguageId) -> Self {
Self {
file_id,
module_path,
language_id,
}
}
pub fn from_file(file_id: FileId, language_id: LanguageId) -> Self {
Self {
file_id,
module_path: None,
language_id,
}
}
pub fn is_same_module(&self, target_module_path: Option<&str>) -> bool {
match (&self.module_path, target_module_path) {
(Some(caller), Some(target)) => {
caller.starts_with(target) || target.starts_with(caller.as_ref())
}
_ => false,
}
}
}
pub trait PipelineSymbolCache: Send + Sync {
fn resolve(
&self,
name: &str,
caller: &CallerContext,
to_range: Option<&Range>,
imports: &[Import],
) -> ResolveResult;
fn get(&self, id: SymbolId) -> Option<crate::Symbol>;
fn symbols_in_file(&self, file_id: FileId) -> Vec<SymbolId>;
fn lookup_candidates(&self, name: &str) -> Vec<SymbolId>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveResult {
Found(SymbolId),
Ambiguous(Vec<SymbolId>),
NotFound,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_compatibility_function_calls_function() {
let context = GenericResolutionContext::new(FileId::new(1).unwrap());
assert!(context.is_compatible_relationship(
crate::SymbolKind::Function,
crate::SymbolKind::Function,
crate::RelationKind::Calls
));
}
#[test]
fn test_default_compatibility_function_cannot_call_constant() {
let context = GenericResolutionContext::new(FileId::new(1).unwrap());
assert!(!context.is_compatible_relationship(
crate::SymbolKind::Function,
crate::SymbolKind::Constant,
crate::RelationKind::Calls
));
}
#[test]
fn test_default_compatibility_class_extends_class() {
let context = GenericResolutionContext::new(FileId::new(1).unwrap());
assert!(context.is_compatible_relationship(
crate::SymbolKind::Class,
crate::SymbolKind::Class,
crate::RelationKind::Extends
));
}
#[test]
fn test_generic_resolution_context() {
let mut ctx = GenericResolutionContext::new(FileId::new(1).unwrap());
ctx.add_symbol(
"local_var".to_string(),
SymbolId::new(1).unwrap(),
ScopeLevel::Local,
);
ctx.add_symbol(
"module_fn".to_string(),
SymbolId::new(2).unwrap(),
ScopeLevel::Module,
);
ctx.add_symbol(
"global_type".to_string(),
SymbolId::new(3).unwrap(),
ScopeLevel::Global,
);
assert_eq!(ctx.resolve("local_var"), Some(SymbolId::new(1).unwrap()));
assert_eq!(ctx.resolve("module_fn"), Some(SymbolId::new(2).unwrap()));
assert_eq!(ctx.resolve("global_type"), Some(SymbolId::new(3).unwrap()));
assert_eq!(ctx.resolve("unknown"), None);
ctx.clear_local_scope();
assert_eq!(ctx.resolve("local_var"), None);
assert_eq!(ctx.resolve("module_fn"), Some(SymbolId::new(2).unwrap()));
}
#[test]
fn test_generic_inheritance_resolver() {
let mut resolver = GenericInheritanceResolver::new();
resolver.add_inheritance("Child".to_string(), "Parent".to_string(), "extends");
resolver.add_inheritance("Parent".to_string(), "GrandParent".to_string(), "extends");
resolver.add_type_methods("GrandParent".to_string(), vec!["method1".to_string()]);
resolver.add_type_methods("Parent".to_string(), vec!["method2".to_string()]);
resolver.add_type_methods("Child".to_string(), vec!["method3".to_string()]);
assert_eq!(
resolver.resolve_method("Child", "method3"),
Some("Child".to_string())
);
assert_eq!(
resolver.resolve_method("Child", "method2"),
Some("Parent".to_string())
);
assert_eq!(
resolver.resolve_method("Child", "method1"),
Some("GrandParent".to_string())
);
let chain = resolver.get_inheritance_chain("Child");
assert!(chain.contains(&"Child".to_string()));
assert!(chain.contains(&"Parent".to_string()));
assert!(chain.contains(&"GrandParent".to_string()));
assert!(resolver.is_subtype("Child", "Parent"));
assert!(resolver.is_subtype("Child", "GrandParent"));
assert!(!resolver.is_subtype("Parent", "Child"));
}
}