use std::collections::HashMap;
use crate::path::SymbolPath;
use crate::registry::SymbolRegistry;
use crate::SymbolId;
#[derive(Debug, Clone, Default)]
pub struct ImportMap {
imports: HashMap<String, SymbolPath>,
glob_imports: Vec<SymbolPath>,
renames: HashMap<String, SymbolPath>,
}
impl ImportMap {
pub fn new() -> Self {
Self::default()
}
pub fn add_import(&mut self, local_name: impl Into<String>, full_path: SymbolPath) {
self.imports.insert(local_name.into(), full_path);
}
pub fn add_rename(&mut self, alias: impl Into<String>, full_path: SymbolPath) {
self.renames.insert(alias.into(), full_path);
}
pub fn add_glob(&mut self, module_path: SymbolPath) {
self.glob_imports.push(module_path);
}
pub fn resolve(&self, name: &str) -> Option<&SymbolPath> {
if let Some(path) = self.imports.get(name) {
return Some(path);
}
self.renames.get(name)
}
pub fn resolve_with_registry(&self, name: &str, registry: &SymbolRegistry) -> Option<SymbolId> {
if let Some(path) = self.resolve(name) {
return registry.lookup(path);
}
for glob_module in &self.glob_imports {
if let Ok(candidate) = glob_module.child(name) {
if let Some(id) = registry.lookup(&candidate) {
return Some(id);
}
}
}
None
}
pub fn imports(&self) -> &HashMap<String, SymbolPath> {
&self.imports
}
pub fn renames(&self) -> &HashMap<String, SymbolPath> {
&self.renames
}
pub fn glob_imports(&self) -> &[SymbolPath] {
&self.glob_imports
}
pub fn is_empty(&self) -> bool {
self.imports.is_empty() && self.renames.is_empty() && self.glob_imports.is_empty()
}
pub fn len(&self) -> usize {
self.imports.len() + self.renames.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct UseResolver {
import_maps: HashMap<SymbolPath, ImportMap>,
}
impl UseResolver {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, module_path: SymbolPath, import_map: ImportMap) {
self.import_maps.insert(module_path, import_map);
}
pub fn get(&self, module_path: &SymbolPath) -> Option<&ImportMap> {
self.import_maps.get(module_path)
}
pub fn get_mut(&mut self, module_path: &SymbolPath) -> Option<&mut ImportMap> {
self.import_maps.get_mut(module_path)
}
pub fn resolve(
&self,
module_path: &SymbolPath,
name: &str,
registry: &SymbolRegistry,
) -> Option<SymbolId> {
if let Some(import_map) = self.import_maps.get(module_path) {
if let Some(id) = import_map.resolve_with_registry(name, registry) {
return Some(id);
}
}
if name.contains("::") {
if let Ok(path) = SymbolPath::parse(name) {
if let Some(id) = registry.lookup(&path) {
return Some(id);
}
}
}
if let Ok(qualified) = module_path.child(name) {
if let Some(id) = registry.lookup(&qualified) {
return Some(id);
}
}
let mut current = module_path.clone();
while let Some(parent) = current.parent() {
if let Ok(qualified) = parent.child(name) {
if let Some(id) = registry.lookup(&qualified) {
return Some(id);
}
}
current = parent;
}
if let Ok(path) = SymbolPath::parse(name) {
return registry.lookup(&path);
}
None
}
pub fn contains(&self, module_path: &SymbolPath) -> bool {
self.import_maps.contains_key(module_path)
}
pub fn len(&self) -> usize {
self.import_maps.len()
}
pub fn is_empty(&self) -> bool {
self.import_maps.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&SymbolPath, &ImportMap)> {
self.import_maps.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SymbolKind;
fn make_path(s: &str) -> SymbolPath {
SymbolPath::parse(s).unwrap()
}
#[test]
fn test_import_map_basic() {
let mut map = ImportMap::new();
let hashmap_path = make_path("std::collections::HashMap");
map.add_import("HashMap", hashmap_path.clone());
assert_eq!(map.resolve("HashMap"), Some(&hashmap_path));
assert_eq!(map.resolve("Vec"), None);
}
#[test]
fn test_import_map_rename() {
let mut map = ImportMap::new();
let hashmap_path = make_path("std::collections::HashMap");
map.add_rename("Map", hashmap_path.clone());
assert_eq!(map.resolve("Map"), Some(&hashmap_path));
assert_eq!(map.resolve("HashMap"), None);
}
#[test]
fn test_import_map_with_registry() {
let mut map = ImportMap::new();
let mut registry = SymbolRegistry::new();
let hashmap_path = make_path("std::collections::HashMap");
let id = registry
.register(hashmap_path.clone(), SymbolKind::Struct)
.unwrap();
map.add_import("HashMap", hashmap_path);
assert_eq!(map.resolve_with_registry("HashMap", ®istry), Some(id));
}
#[test]
fn test_import_map_glob() {
let mut map = ImportMap::new();
let mut registry = SymbolRegistry::new();
let foo_bar_path = make_path("foo::bar::Baz");
let id = registry.register(foo_bar_path, SymbolKind::Struct).unwrap();
map.add_glob(make_path("foo::bar"));
assert_eq!(map.resolve_with_registry("Baz", ®istry), Some(id));
}
#[test]
fn test_use_resolver_basic() {
let mut resolver = UseResolver::new();
let mut registry = SymbolRegistry::new();
let hashmap_path = make_path("std::collections::HashMap");
let id = registry
.register(hashmap_path.clone(), SymbolKind::Struct)
.unwrap();
let module_path = make_path("my_crate::handlers");
let mut import_map = ImportMap::new();
import_map.add_import("HashMap", hashmap_path);
resolver.register(module_path.clone(), import_map);
assert_eq!(
resolver.resolve(&module_path, "HashMap", ®istry),
Some(id)
);
}
#[test]
fn test_use_resolver_hierarchy_fallback() {
let mut resolver = UseResolver::new();
let mut registry = SymbolRegistry::new();
let config_path = make_path("my_crate::Config");
let id = registry.register(config_path, SymbolKind::Struct).unwrap();
let module_path = make_path("my_crate::handlers::create");
resolver.register(module_path.clone(), ImportMap::new());
assert_eq!(
resolver.resolve(&module_path, "Config", ®istry),
Some(id)
);
}
#[test]
fn test_use_resolver_qualified_path() {
let resolver = UseResolver::new();
let mut registry = SymbolRegistry::new();
let path = make_path("std::io::Read");
let id = registry.register(path, SymbolKind::Trait).unwrap();
let module_path = make_path("my_crate");
assert_eq!(
resolver.resolve(&module_path, "std::io::Read", ®istry),
Some(id)
);
}
}